@effect-app/infra 4.0.0-beta.99 → 4.0.1
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 +2901 -0
- package/_check.sh +1 -1
- package/dist/CUPS.d.ts +28 -9
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +35 -14
- package/dist/ClusterCosmos.d.ts +67 -0
- package/dist/ClusterCosmos.d.ts.map +1 -0
- package/dist/ClusterCosmos.js +588 -0
- package/dist/ClusterServiceBus.d.ts +67 -0
- package/dist/ClusterServiceBus.d.ts.map +1 -0
- package/dist/ClusterServiceBus.js +84 -0
- package/dist/{api/ContextProvider.d.ts → ContextProvider.d.ts} +7 -4
- 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 +11 -7
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +22 -6
- package/dist/QueueMaker/errors.d.ts +3 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/errors.js +4 -2
- package/dist/{adapters/SQL → SQL}/Model.d.ts +34 -45
- 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 +16 -4
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +240 -44
- package/dist/Store/Cosmos.d.ts +2 -2
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +394 -320
- package/dist/Store/Disk.d.ts +4 -4
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +91 -39
- package/dist/Store/Memory.d.ts +7 -10
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +347 -78
- package/dist/Store/SQL/Pg.d.ts +2 -2
- package/dist/Store/SQL/Pg.d.ts.map +1 -1
- package/dist/Store/SQL/Pg.js +230 -155
- package/dist/Store/SQL/query.d.ts +19 -3
- package/dist/Store/SQL/query.d.ts.map +1 -1
- package/dist/Store/SQL/query.js +308 -14
- package/dist/Store/SQL.d.ts +22 -5
- package/dist/Store/SQL.d.ts.map +1 -1
- package/dist/Store/SQL.js +370 -99
- package/dist/Store/codeFilter.d.ts +5 -5
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +72 -23
- package/dist/Store/index.d.ts +6 -4
- package/dist/Store/index.d.ts.map +1 -1
- package/dist/Store/index.js +9 -6
- package/dist/Store/jsonDocument.d.ts +67 -0
- package/dist/Store/jsonDocument.d.ts.map +1 -0
- package/dist/Store/jsonDocument.js +107 -0
- package/dist/Store/utils.d.ts +21 -2
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +253 -10
- package/dist/WorkflowEngineCosmos.d.ts +30 -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 +6 -3
- package/dist/arbs.d.ts.map +1 -1
- package/dist/arbs.js +20 -4
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +5 -0
- package/dist/cosmos-client.d.ts +40 -0
- package/dist/cosmos-client.d.ts.map +1 -0
- package/dist/cosmos-client.js +41 -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/{api/internal → internal}/health.d.ts +1 -1
- 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} +10 -2
- 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 +6 -6
- 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} +2 -1
- 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 +84 -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} +4 -1
- 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 +2 -1
- package/dist/routing/middleware/RouterMiddleware.d.ts.map +1 -0
- package/dist/{api/routing → routing}/middleware/RouterMiddleware.js +1 -1
- package/dist/{api/routing → routing}/middleware/middleware.d.ts +11 -6
- 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 +12 -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 +257 -0
- package/dist/test.d.ts +2 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +5 -5
- 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 +23 -19
- package/package.json +36 -364
- package/src/CUPS.ts +53 -14
- package/src/ClusterCosmos.ts +1170 -0
- package/src/ClusterServiceBus.ts +244 -0
- package/src/{api/ContextProvider.ts → ContextProvider.ts} +10 -7
- package/src/Emailer/Sendgrid.ts +83 -60
- package/src/Emailer/fake.ts +4 -4
- package/src/MainFiberSet.ts +31 -12
- package/src/QueueMaker/errors.ts +3 -1
- package/src/{adapters/SQL → SQL}/Model.ts +48 -47
- package/src/SQL.ts +1 -0
- package/src/Store/Cosmos/query.ts +277 -57
- package/src/Store/Cosmos.ts +597 -512
- package/src/Store/Disk.ts +145 -71
- package/src/Store/Memory.ts +419 -115
- package/src/Store/SQL/Pg.ts +352 -269
- package/src/Store/SQL/query.ts +368 -22
- package/src/Store/SQL.ts +742 -294
- package/src/Store/codeFilter.ts +79 -29
- package/src/Store/index.ts +18 -13
- package/src/Store/jsonDocument.ts +143 -0
- package/src/Store/utils.ts +307 -30
- package/src/WorkflowEngineCosmos.ts +749 -0
- package/src/WorkflowEngineSqlite.ts +813 -0
- package/src/arbs.ts +25 -5
- package/src/cosmos-client.ts +72 -0
- package/src/errorReporter.ts +70 -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 +18 -9
- package/src/{api/layerUtils.ts → layerUtils.ts} +7 -3
- package/src/logger/jsonLogger.ts +5 -3
- package/src/logger/logFmtLogger.ts +3 -3
- package/src/logger/shared.ts +6 -5
- package/src/middlewares.ts +10 -0
- package/src/{adapters/mongo-client.ts → mongo-client.ts} +3 -1
- package/src/otel.ts +177 -0
- package/src/rateLimit.ts +35 -24
- package/src/{adapters/redis-client.ts → redis-client.ts} +6 -2
- package/src/{api/reportError.ts → reportError.ts} +3 -2
- package/src/{api/routing → routing}/middleware/RouterMiddleware.ts +2 -1
- package/src/{api/routing → routing}/middleware/middleware.ts +75 -50
- package/src/routing/middleware.ts +4 -0
- package/src/{api/routing → routing}/schema/jwt.ts +2 -2
- package/src/{api/routing → routing}/utils.ts +2 -1
- package/src/routing.ts +827 -0
- package/src/test.ts +6 -6
- package/src/vitest.ts +1 -1
- package/test/arbs-magnitude.test.ts +33 -0
- package/test/arbs.test.ts +41 -0
- package/test/auth.test.ts +101 -0
- package/test/cluster-cosmos.test.ts +634 -0
- package/test/cluster-servicebus.test.ts +180 -0
- package/test/cluster-sqlite.test.ts +207 -0
- package/test/contextProvider.test.ts +11 -8
- package/test/controller.test.ts +23 -28
- package/test/cosmos-container-throughput.test.ts +80 -0
- package/test/cosmos-query.test.ts +368 -0
- package/test/dist/arbs-magnitude.test.d.ts.map +1 -0
- package/test/dist/arbs.test.d.ts.map +1 -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-container-throughput.test.d.ts.map +1 -0
- package/test/dist/cosmos-query.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +17 -9
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +8 -3
- package/test/dist/json-lower.test.d.ts.map +1 -0
- 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/repository-legacy-document.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/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 +7 -2
- package/test/json-lower.test.ts +129 -0
- package/test/layerUtils.test.ts +2 -2
- package/test/query.test.ts +1232 -42
- package/test/rawQuery.test.ts +432 -7
- package/test/repository-ext.test.ts +456 -0
- package/test/repository-legacy-document.test.ts +394 -0
- package/test/requires.test.ts +7 -3
- package/test/router-generator.test.ts +187 -0
- package/test/routing-interruptibility.test.ts +66 -0
- package/test/rpc-context-map-streaming.test.ts +265 -0
- package/test/rpc-e2e-invalidation.test.ts +421 -0
- package/test/rpc-multi-middleware.test.ts +17 -11
- package/test/rpc-stream-fullstack.test.ts +307 -0
- package/test/sql-store.test.ts +1202 -47
- package/test/validateSample.test.ts +70 -42
- 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 +9 -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 -320
- 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 -43
- 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 -14
- package/dist/QueueMaker/SQLQueue.d.ts.map +0 -1
- package/dist/QueueMaker/SQLQueue.js +0 -132
- 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 -68
- 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 -25
- package/dist/Store/ContextMapContainer.d.ts.map +0 -1
- package/dist/Store/ContextMapContainer.js +0 -77
- package/dist/Store/service.d.ts +0 -111
- package/dist/Store/service.d.ts.map +0 -1
- package/dist/Store/service.js +0 -79
- 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 +0 -14
- 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.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.map +0 -1
- package/dist/api/routing/middleware/middleware.js +0 -130
- 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 -134
- package/dist/api/routing.d.ts.map +0 -1
- package/dist/api/routing.js +0 -167
- package/dist/api/setupRequest.d.ts +0 -15
- package/dist/api/setupRequest.d.ts.map +0 -1
- package/dist/api/setupRequest.js +0 -49
- 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 -580
- 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 -190
- 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 -116
- package/src/Store/index.test.ts.bak +0 -112
- package/src/Store/service.ts +0 -193
- package/src/Store.ts +0 -1
- package/src/adapters/SQL.ts +0 -1
- package/src/adapters/ServiceBus.ts +0 -209
- package/src/adapters/cosmos-client.ts +0 -16
- 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.ts +0 -6
- package/src/api/routing.ts +0 -598
- package/src/api/setupRequest.ts +0 -104
- package/test/dist/date-query.test.d.ts.map +0 -1
- 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/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,40 @@
|
|
|
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
|
|
5
|
-
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, type ProjectableFromDomain, 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 Redacted from "effect/Redacted"
|
|
15
|
+
import * as Getter from "effect/SchemaGetter"
|
|
16
|
+
import * as SchemaTransformation from "effect/SchemaTransformation"
|
|
17
|
+
import * as Struct from "effect/Struct"
|
|
18
|
+
import * as fs from "fs"
|
|
19
|
+
import * as os from "os"
|
|
20
|
+
import * as path from "path"
|
|
6
21
|
import { inspect } from "util"
|
|
7
22
|
import { expect, expectTypeOf, it } from "vitest"
|
|
8
|
-
import {
|
|
9
|
-
import { and, count, make, one, or, order, page, project, type QueryEnd, type QueryProjection, type QueryWhere, toFilter, where } from "../src/Model/query.js"
|
|
10
|
-
import { makeRepo } from "../src/Model/Repository.js"
|
|
23
|
+
import { DiskStoreLayer } from "../src/Store/Disk.js"
|
|
11
24
|
import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
|
|
12
25
|
import { SomeService } from "./fixtures.js"
|
|
13
26
|
|
|
27
|
+
const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
|
|
28
|
+
|
|
14
29
|
const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
|
|
15
30
|
const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
|
|
16
31
|
const someUnion = S.Union([str, num])
|
|
17
32
|
|
|
18
33
|
export class Something extends S.Class<Something>("Something")({
|
|
19
|
-
id: S.StringId.
|
|
34
|
+
id: S.StringId.withConstructorDefault,
|
|
20
35
|
displayName: S.NonEmptyString255,
|
|
21
|
-
name: S.NullOr(S.NonEmptyString255).
|
|
22
|
-
n: S.Date.
|
|
36
|
+
name: S.NullOr(S.NonEmptyString255).withConstructorDefault,
|
|
37
|
+
n: S.Date.withConstructorDefault,
|
|
23
38
|
union: someUnion.pipe(S.withConstructorDefault(Effect.succeed({ _tag: "string" as const, value: "hi" })))
|
|
24
39
|
}) {}
|
|
25
40
|
export declare namespace Something {
|
|
@@ -32,7 +47,7 @@ const q = make<Something.Encoded>()
|
|
|
32
47
|
where("displayName", "Verona"),
|
|
33
48
|
or(
|
|
34
49
|
where("displayName", "Riley"),
|
|
35
|
-
and("n", "gt", "2021-01-01T00:00:00Z")
|
|
50
|
+
and("n", "gt", new Date("2021-01-01T00:00:00Z"))
|
|
36
51
|
),
|
|
37
52
|
order("displayName"),
|
|
38
53
|
page({ take: 10 }),
|
|
@@ -113,7 +128,7 @@ class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
|
|
|
113
128
|
})
|
|
114
129
|
)
|
|
115
130
|
.pipe(
|
|
116
|
-
Layer.provide(
|
|
131
|
+
Layer.provide(TestStoreLive)
|
|
117
132
|
)
|
|
118
133
|
}
|
|
119
134
|
|
|
@@ -132,7 +147,7 @@ it("works with repo", () =>
|
|
|
132
147
|
where("displayName", "Verona"),
|
|
133
148
|
or(
|
|
134
149
|
where("displayName", "Riley"),
|
|
135
|
-
and("n", "gt", "2021-01-01T00:00:00Z")
|
|
150
|
+
and("n", "gt", new Date("2021-01-01T00:00:00Z"))
|
|
136
151
|
),
|
|
137
152
|
order("displayName"),
|
|
138
153
|
page({ take: 10 }),
|
|
@@ -159,13 +174,163 @@ it("works with repo", () =>
|
|
|
159
174
|
|
|
160
175
|
expect(q1).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["id", "displayName"])))
|
|
161
176
|
expect(q2).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["displayName"])))
|
|
177
|
+
|
|
178
|
+
const byDate = yield* somethingRepo.query(
|
|
179
|
+
where("n", new Date("2020-01-01T00:00:00.000Z"))
|
|
180
|
+
)
|
|
181
|
+
expect(byDate.map((_) => _.displayName)).toEqual(["Verona", "Riley"])
|
|
162
182
|
})
|
|
163
183
|
.pipe(
|
|
164
184
|
Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
|
|
165
185
|
setupRequestContextFromCurrent(),
|
|
186
|
+
Effect.scoped,
|
|
166
187
|
Effect.runPromise
|
|
167
188
|
))
|
|
168
189
|
|
|
190
|
+
it("memory store round-trips Date/Set/Map via JSON codecs", () =>
|
|
191
|
+
Effect
|
|
192
|
+
.gen(function*() {
|
|
193
|
+
class Doc extends S.Class<Doc>("JsonCodecDoc")({
|
|
194
|
+
id: S.String,
|
|
195
|
+
at: S.Date,
|
|
196
|
+
tags: S.ReadonlySet(S.String),
|
|
197
|
+
meta: S.ReadonlyMap({ key: S.String, value: S.Finite })
|
|
198
|
+
}) {}
|
|
199
|
+
const at = new Date("2024-06-01T00:00:00.000Z")
|
|
200
|
+
const saved = new Doc({
|
|
201
|
+
id: "d1",
|
|
202
|
+
at,
|
|
203
|
+
tags: new Set(["a", "b"]),
|
|
204
|
+
meta: new Map([["n", 1]])
|
|
205
|
+
})
|
|
206
|
+
const repo = yield* makeRepo("JsonCodecDoc", Doc, { makeInitial: Effect.succeed([saved]) })
|
|
207
|
+
const found = yield* repo.find("d1")
|
|
208
|
+
expect(Option.isSome(found)).toBe(true)
|
|
209
|
+
if (Option.isSome(found)) {
|
|
210
|
+
expect(found.value.at.toISOString()).toBe(at.toISOString())
|
|
211
|
+
expect(found.value.tags).toEqual(new Set(["a", "b"]))
|
|
212
|
+
expect(found.value.meta).toEqual(new Map([["n", 1]]))
|
|
213
|
+
}
|
|
214
|
+
const byDate = yield* repo.query(where("at", at))
|
|
215
|
+
expect(byDate.map((_) => _.id)).toEqual(["d1"])
|
|
216
|
+
const byTag = yield* repo.query(where("tags", "includes", "b"))
|
|
217
|
+
expect(byTag.map((_) => _.id)).toEqual(["d1"])
|
|
218
|
+
const byKey = yield* repo.query(where("meta", "hasKey", "n"))
|
|
219
|
+
expect(byKey.map((_) => _.id)).toEqual(["d1"])
|
|
220
|
+
const byPair = yield* repo.query(where("meta", "hasKeyValue", ["n", 1]))
|
|
221
|
+
expect(byPair.map((_) => _.id)).toEqual(["d1"])
|
|
222
|
+
})
|
|
223
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
224
|
+
|
|
225
|
+
class Day {
|
|
226
|
+
readonly ymd: string
|
|
227
|
+
constructor(ymd: string) {
|
|
228
|
+
this.ymd = ymd
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const DayFromSelf = S.declare((u): u is Day => u instanceof Day, {
|
|
233
|
+
expected: "Day",
|
|
234
|
+
toCodecJson: () =>
|
|
235
|
+
S.link<Day>()(
|
|
236
|
+
S.String,
|
|
237
|
+
{
|
|
238
|
+
decode: Getter.transform((s: string) => new Day(s)),
|
|
239
|
+
encode: Getter.transform((d: Day) => d.ymd)
|
|
240
|
+
}
|
|
241
|
+
)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it("memory store round-trips app native Encoded values via the document schema", () =>
|
|
245
|
+
Effect
|
|
246
|
+
.gen(function*() {
|
|
247
|
+
class Doc extends S.Class<Doc>("JsonCodecDayDoc")({
|
|
248
|
+
id: S.String,
|
|
249
|
+
day: DayFromSelf
|
|
250
|
+
}) {}
|
|
251
|
+
const day = new Day("2024-06-01")
|
|
252
|
+
const saved = new Doc({ id: "d1", day })
|
|
253
|
+
const repo = yield* makeRepo("JsonCodecDayDoc", Doc, { makeInitial: Effect.succeed([saved]) })
|
|
254
|
+
const found = yield* repo.find("d1")
|
|
255
|
+
expect(Option.isSome(found)).toBe(true)
|
|
256
|
+
if (Option.isSome(found)) {
|
|
257
|
+
expect(found.value.day).toBeInstanceOf(Day)
|
|
258
|
+
expect(found.value.day.ymd).toBe("2024-06-01")
|
|
259
|
+
}
|
|
260
|
+
expect(S.encodeSync(DayFromSelf)(day)).toBe(day)
|
|
261
|
+
expect(S.encodeSync(S.toCodecJson(DayFromSelf))(day)).toBe("2024-06-01")
|
|
262
|
+
const byDay = yield* repo.query(where("day", day))
|
|
263
|
+
expect(byDay.map((_) => _.id)).toEqual(["d1"])
|
|
264
|
+
})
|
|
265
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
266
|
+
|
|
267
|
+
it("memory store queries encodeKeys-renamed native Encoded fields", () =>
|
|
268
|
+
Effect
|
|
269
|
+
.gen(function*() {
|
|
270
|
+
class Doc extends S.Class<Doc>("JsonCodecEncodeKeysDayDoc")({
|
|
271
|
+
id: S.String,
|
|
272
|
+
day: DayFromSelf
|
|
273
|
+
}) {}
|
|
274
|
+
const schema = Doc.pipe(S.encodeKeys({ day: "the_day" }))
|
|
275
|
+
const day = new Day("2024-06-01")
|
|
276
|
+
const saved = new Doc({ id: "d1", day })
|
|
277
|
+
const repo = yield* makeRepo("JsonCodecEncodeKeysDayDoc", schema, { makeInitial: Effect.succeed([saved]) })
|
|
278
|
+
const byDay = yield* repo.query(where("the_day", day))
|
|
279
|
+
expect(byDay.map((_) => _.id)).toEqual(["d1"])
|
|
280
|
+
const byId = yield* repo.query(where("id", "in", ["d1"]))
|
|
281
|
+
expect(byId.map((_) => _.id)).toEqual(["d1"])
|
|
282
|
+
})
|
|
283
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
284
|
+
|
|
285
|
+
it("disk store round-trips Date/Set/Map via JSON codecs", () => {
|
|
286
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "effect-app-disk-json-"))
|
|
287
|
+
const diskLive = Layer.merge(
|
|
288
|
+
DiskStoreLayer({ url: Redacted.make(`disk://${dir}`), prefix: "", dbName: "test" }, dir),
|
|
289
|
+
RepositoryRegistryLive
|
|
290
|
+
)
|
|
291
|
+
return Effect
|
|
292
|
+
.gen(function*() {
|
|
293
|
+
class Doc extends S.Class<Doc>("JsonCodecDiskDoc")({
|
|
294
|
+
id: S.String,
|
|
295
|
+
at: S.Date,
|
|
296
|
+
tags: S.ReadonlySet(S.String),
|
|
297
|
+
meta: S.ReadonlyMap({ key: S.String, value: S.Finite })
|
|
298
|
+
}) {}
|
|
299
|
+
const at = new Date("2024-06-01T00:00:00.000Z")
|
|
300
|
+
const saved = new Doc({
|
|
301
|
+
id: "d1",
|
|
302
|
+
at,
|
|
303
|
+
tags: new Set(["a", "b"]),
|
|
304
|
+
meta: new Map([["n", 1]])
|
|
305
|
+
})
|
|
306
|
+
const repo = yield* makeRepo("JsonCodecDiskDoc", Doc, { makeInitial: Effect.succeed([saved]) })
|
|
307
|
+
const found = yield* repo.find("d1")
|
|
308
|
+
expect(Option.isSome(found)).toBe(true)
|
|
309
|
+
if (Option.isSome(found)) {
|
|
310
|
+
expect(found.value.at.toISOString()).toBe(at.toISOString())
|
|
311
|
+
expect(found.value.tags).toEqual(new Set(["a", "b"]))
|
|
312
|
+
expect(found.value.meta).toEqual(new Map([["n", 1]]))
|
|
313
|
+
}
|
|
314
|
+
const jsonFile = fs.readdirSync(dir).find((f) => f.endsWith(".json"))
|
|
315
|
+
expect(jsonFile).toBeDefined()
|
|
316
|
+
const raw = JSON.parse(fs.readFileSync(path.join(dir, jsonFile!), "utf8")) as Array<{
|
|
317
|
+
at: unknown
|
|
318
|
+
tags: unknown
|
|
319
|
+
meta: unknown
|
|
320
|
+
}>
|
|
321
|
+
expect(raw[0]?.at).toBe(at.toISOString())
|
|
322
|
+
expect(raw[0]?.tags).toEqual(["a", "b"])
|
|
323
|
+
expect(raw[0]?.meta).toEqual([["n", 1]])
|
|
324
|
+
})
|
|
325
|
+
.pipe(
|
|
326
|
+
Effect.provide(diskLive),
|
|
327
|
+
setupRequestContextFromCurrent(),
|
|
328
|
+
Effect.scoped,
|
|
329
|
+
Effect.runPromise
|
|
330
|
+
)
|
|
331
|
+
.finally(() => fs.rmSync(dir, { recursive: true, force: true }))
|
|
332
|
+
})
|
|
333
|
+
|
|
169
334
|
it("collect", () =>
|
|
170
335
|
Effect
|
|
171
336
|
.gen(function*() {
|
|
@@ -186,8 +351,8 @@ it("collect", () =>
|
|
|
186
351
|
})),
|
|
187
352
|
S.toType(S.Option(S.String)),
|
|
188
353
|
(_) =>
|
|
189
|
-
_.displayName === "Riley" && _.n === "2020-01-01T00:00:00.000Z"
|
|
190
|
-
? Option.some(`${_.displayName}-${_.n}`)
|
|
354
|
+
_.displayName === "Riley" && _.n.toISOString() === "2020-01-01T00:00:00.000Z"
|
|
355
|
+
? Option.some(`${_.displayName}-${_.n.toISOString()}`)
|
|
191
356
|
: Option.none()
|
|
192
357
|
),
|
|
193
358
|
"collect"
|
|
@@ -205,7 +370,7 @@ it("collect", () =>
|
|
|
205
370
|
QueryEnd<{
|
|
206
371
|
readonly id: string
|
|
207
372
|
readonly displayName: string
|
|
208
|
-
readonly n:
|
|
373
|
+
readonly n: Date
|
|
209
374
|
readonly union: {
|
|
210
375
|
readonly _tag: "string"
|
|
211
376
|
readonly value: string
|
|
@@ -234,18 +399,19 @@ it("collect", () =>
|
|
|
234
399
|
.pipe(
|
|
235
400
|
Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
|
|
236
401
|
setupRequestContextFromCurrent(),
|
|
402
|
+
Effect.scoped,
|
|
237
403
|
Effect.runPromise
|
|
238
404
|
))
|
|
239
405
|
|
|
240
|
-
class Person extends S.
|
|
406
|
+
class Person extends S.TaggedClass<Person, Person.Encoded>()("person", {
|
|
241
407
|
id: S.String,
|
|
242
408
|
surname: S.String
|
|
243
409
|
}) {}
|
|
244
|
-
class Animal extends S.
|
|
410
|
+
class Animal extends S.TaggedClass<Animal, Animal.Encoded>()("animal", {
|
|
245
411
|
id: S.String,
|
|
246
412
|
surname: S.String
|
|
247
413
|
}) {}
|
|
248
|
-
class Test extends S.
|
|
414
|
+
class Test extends S.TaggedClass<Test, Test.Encoded>()("test", {
|
|
249
415
|
id: S.String
|
|
250
416
|
}) {}
|
|
251
417
|
|
|
@@ -280,7 +446,7 @@ it(
|
|
|
280
446
|
expect(result).toEqual([])
|
|
281
447
|
expect(result2).toEqual([])
|
|
282
448
|
})
|
|
283
|
-
.pipe(Effect.provide(
|
|
449
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
284
450
|
)
|
|
285
451
|
|
|
286
452
|
it(
|
|
@@ -378,7 +544,6 @@ it(
|
|
|
378
544
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
379
545
|
const query5 = make<Union>().pipe(
|
|
380
546
|
where("id", "bla"),
|
|
381
|
-
// @ts-expect-error cannot project over fields that are not in common between the union members (you must refine the union first)
|
|
382
547
|
project(S.Struct({ id: S.String, a: S.Unknown }))
|
|
383
548
|
)
|
|
384
549
|
console.log(query5)
|
|
@@ -466,7 +631,7 @@ it(
|
|
|
466
631
|
|
|
467
632
|
expect([]).toEqual([])
|
|
468
633
|
})
|
|
469
|
-
.pipe(Effect.provide(
|
|
634
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
470
635
|
)
|
|
471
636
|
|
|
472
637
|
it(
|
|
@@ -509,7 +674,7 @@ it(
|
|
|
509
674
|
|
|
510
675
|
expect([]).toEqual([])
|
|
511
676
|
})
|
|
512
|
-
.pipe(Effect.provide(
|
|
677
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
513
678
|
)
|
|
514
679
|
|
|
515
680
|
it(
|
|
@@ -520,7 +685,7 @@ it(
|
|
|
520
685
|
const schema = S.Struct({
|
|
521
686
|
id: S.String,
|
|
522
687
|
createdAt: S.Date.pipe(
|
|
523
|
-
S.withDecodingDefault(Effect.sync(() => new Date()
|
|
688
|
+
S.withDecodingDefault(Effect.sync(() => new Date())),
|
|
524
689
|
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
525
690
|
)
|
|
526
691
|
})
|
|
@@ -533,7 +698,7 @@ it(
|
|
|
533
698
|
const outputSchema = S.Struct({
|
|
534
699
|
id: S.Literal("123"),
|
|
535
700
|
createdAt: S.Date.pipe(
|
|
536
|
-
S.withDecodingDefault(Effect.sync(() => new Date()
|
|
701
|
+
S.withDecodingDefault(Effect.sync(() => new Date())),
|
|
537
702
|
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
538
703
|
)
|
|
539
704
|
})
|
|
@@ -542,9 +707,470 @@ it(
|
|
|
542
707
|
|
|
543
708
|
expect(result).toEqual([])
|
|
544
709
|
})
|
|
545
|
-
.pipe(Effect.provide(
|
|
710
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
546
711
|
)
|
|
547
712
|
|
|
713
|
+
it(
|
|
714
|
+
"project with encodeKeys in projection maps encoded keys",
|
|
715
|
+
() =>
|
|
716
|
+
Effect
|
|
717
|
+
.gen(function*() {
|
|
718
|
+
const schema = S.Struct({
|
|
719
|
+
id: S.String,
|
|
720
|
+
a: S.Number
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
const repo = yield* makeRepo(
|
|
724
|
+
"test",
|
|
725
|
+
schema,
|
|
726
|
+
{
|
|
727
|
+
makeInitial: Effect.sync(() => [{ id: "1", a: 1 }])
|
|
728
|
+
}
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
const outputSchema = S.Struct({ b: S.Number }).pipe(S.encodeKeys({ b: "a" }))
|
|
732
|
+
|
|
733
|
+
const result = yield* repo.query(project(outputSchema))
|
|
734
|
+
|
|
735
|
+
expect(result).toStrictEqual([{ b: 1 }])
|
|
736
|
+
})
|
|
737
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
it("projectComputed sets computed IR and forces project mode", () => {
|
|
741
|
+
const baseSchema = S.Struct({
|
|
742
|
+
id: S.String,
|
|
743
|
+
items: S.Array(S.Struct({
|
|
744
|
+
state: S.Struct({
|
|
745
|
+
_tag: S.String
|
|
746
|
+
})
|
|
747
|
+
}))
|
|
748
|
+
})
|
|
749
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
750
|
+
projectComputed(
|
|
751
|
+
S.Struct({
|
|
752
|
+
pickedCount: S.NonNegativeInt
|
|
753
|
+
}),
|
|
754
|
+
computed({
|
|
755
|
+
pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count(where("state._tag", "Picked"))
|
|
756
|
+
})
|
|
757
|
+
)
|
|
758
|
+
)
|
|
759
|
+
const interpreted = toFilter(query, baseSchema)
|
|
760
|
+
expect(interpreted.mode).toBe("project")
|
|
761
|
+
expect(interpreted.select).toEqual([
|
|
762
|
+
{
|
|
763
|
+
key: "pickedCount",
|
|
764
|
+
computed: {
|
|
765
|
+
_tag: "relation-count",
|
|
766
|
+
path: "items",
|
|
767
|
+
filter: [{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }]
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
])
|
|
771
|
+
expect(interpreted.computed?.["pickedCount"]?._tag).toBe("relation-count")
|
|
772
|
+
expect(interpreted.computed?.["pickedCount"]?.path).toBe("items")
|
|
773
|
+
expect((interpreted.computed?.["pickedCount"] as { filter: unknown } | undefined)?.filter).toEqual([
|
|
774
|
+
{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
|
|
775
|
+
])
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
it("projectComputed validates extra computed keys", () => {
|
|
779
|
+
const baseSchema = S.Struct({
|
|
780
|
+
id: S.String,
|
|
781
|
+
items: S.Array(S.Struct({ value: S.Number }))
|
|
782
|
+
})
|
|
783
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
784
|
+
projectComputed(
|
|
785
|
+
S.Struct({ id: S.String }),
|
|
786
|
+
// @ts-expect-error extra computed keys are rejected statically; keep runtime guard covered
|
|
787
|
+
computed({
|
|
788
|
+
pickedCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").count()
|
|
789
|
+
})
|
|
790
|
+
)
|
|
791
|
+
)
|
|
792
|
+
expect(() => toFilter(query, baseSchema)).toThrowError("Computed projection keys must exist in projection schema")
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
it("projectComputed constrains projection schema to encoded repo fields and computed outputs", () => {
|
|
796
|
+
const baseSchema = S.Struct({
|
|
797
|
+
id: S.String,
|
|
798
|
+
name: S.String,
|
|
799
|
+
items: S.Array(S.Struct({ articleId: S.String, weight: S.Number }))
|
|
800
|
+
})
|
|
801
|
+
type Encoded = S.Codec.Encoded<typeof baseSchema>
|
|
802
|
+
|
|
803
|
+
make<Encoded>().pipe(
|
|
804
|
+
projectComputed(
|
|
805
|
+
S.Struct({
|
|
806
|
+
id: S.String,
|
|
807
|
+
itemCount: S.Number,
|
|
808
|
+
hasItems: S.Boolean,
|
|
809
|
+
articleIds: S.Array(S.String)
|
|
810
|
+
}),
|
|
811
|
+
({ relation }) => ({
|
|
812
|
+
itemCount: relation("items").count(),
|
|
813
|
+
hasItems: relation("items").any(),
|
|
814
|
+
articleIds: relation("items").collect("articleId")
|
|
815
|
+
})
|
|
816
|
+
)
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
make<Encoded>().pipe(
|
|
820
|
+
// @ts-expect-error missingField is neither an encoded repo field nor a computed projection
|
|
821
|
+
projectComputed(S.Struct({ missingField: S.String }), computed({}))
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
// Key presence only (not Encoded value types) — name: number is still projectable.
|
|
825
|
+
make<Encoded>().pipe(
|
|
826
|
+
projectComputed(
|
|
827
|
+
S.Struct({ name: S.Number }),
|
|
828
|
+
computed({})
|
|
829
|
+
)
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
// Computed field *value* types are not yet constrained against the expression
|
|
833
|
+
// result (only key presence). Keeping this as a documentation call site.
|
|
834
|
+
make<Encoded>().pipe(
|
|
835
|
+
projectComputed(
|
|
836
|
+
S.Struct({ itemCount: S.String }),
|
|
837
|
+
computed({ itemCount: relation<Encoded>("items").count() })
|
|
838
|
+
)
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
make<Encoded>().pipe(
|
|
842
|
+
projectComputed(
|
|
843
|
+
S.Struct({ itemCount: S.Number }),
|
|
844
|
+
// @ts-expect-error extra computed keys must exist in the projection schema
|
|
845
|
+
computed({
|
|
846
|
+
itemCount: relation<Encoded>("items").count(),
|
|
847
|
+
extraCount: relation<Encoded>("items").count()
|
|
848
|
+
})
|
|
849
|
+
)
|
|
850
|
+
)
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
it("projectComputed supports union projection schemas", () => {
|
|
854
|
+
const baseSchema = S.Union([
|
|
855
|
+
S.Struct({
|
|
856
|
+
_tag: S.Literal("open"),
|
|
857
|
+
id: S.String,
|
|
858
|
+
items: S.Array(S.Struct({ articleId: S.String, weight: S.Number }))
|
|
859
|
+
}),
|
|
860
|
+
S.Struct({
|
|
861
|
+
_tag: S.Literal("closed"),
|
|
862
|
+
id: S.String,
|
|
863
|
+
closedAt: S.String
|
|
864
|
+
})
|
|
865
|
+
])
|
|
866
|
+
type Encoded = S.Codec.Encoded<typeof baseSchema>
|
|
867
|
+
const query = make<Encoded>().pipe(
|
|
868
|
+
projectComputed(
|
|
869
|
+
S.Union([
|
|
870
|
+
S.Struct({
|
|
871
|
+
_tag: S.Literal("open"),
|
|
872
|
+
id: S.String,
|
|
873
|
+
articleCount: S.Number,
|
|
874
|
+
weight: S.Number,
|
|
875
|
+
articleIds: S.Array(S.String)
|
|
876
|
+
}),
|
|
877
|
+
S.Struct({
|
|
878
|
+
_tag: S.Literal("closed"),
|
|
879
|
+
id: S.String,
|
|
880
|
+
closedAt: S.String
|
|
881
|
+
})
|
|
882
|
+
]),
|
|
883
|
+
computed({
|
|
884
|
+
articleCount: relation<Encoded>("items").count(),
|
|
885
|
+
weight: relation<Encoded>("items").sum("weight"),
|
|
886
|
+
articleIds: relation<Encoded>("items").collect("articleId")
|
|
887
|
+
})
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
const interpreted = toFilter(query, baseSchema)
|
|
892
|
+
expect(interpreted.computed && Object.keys(interpreted.computed).toSorted()).toEqual([
|
|
893
|
+
"articleCount",
|
|
894
|
+
"articleIds",
|
|
895
|
+
"weight"
|
|
896
|
+
])
|
|
897
|
+
})
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* MACS-SCANNER-API-KS class of bug: domain cancel omits pack/print ownership
|
|
901
|
+
* (`activeRequest`); a hand-built overview projection that still requires it on
|
|
902
|
+
* every branch must fail at the `projectComputed` call site, not in prod decode.
|
|
903
|
+
*/
|
|
904
|
+
it("projectComputed rejects state-owned fields required on the wrong tagged branch", () => {
|
|
905
|
+
const domain = S.Union([
|
|
906
|
+
S.Struct({
|
|
907
|
+
_tag: S.Literal("packing"),
|
|
908
|
+
id: S.String,
|
|
909
|
+
activeRequest: S.NullOr(S.String),
|
|
910
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
911
|
+
}),
|
|
912
|
+
S.Struct({
|
|
913
|
+
_tag: S.Literal("cancelled"),
|
|
914
|
+
id: S.String,
|
|
915
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
916
|
+
})
|
|
917
|
+
])
|
|
918
|
+
type DomainEnc = S.Codec.Encoded<typeof domain>
|
|
919
|
+
|
|
920
|
+
// Good: activeRequest only on packing; cancel omits it; articleCount is computed.
|
|
921
|
+
make<DomainEnc>().pipe(
|
|
922
|
+
projectComputed(
|
|
923
|
+
S.Union([
|
|
924
|
+
S.Struct({
|
|
925
|
+
_tag: S.Literal("packing"),
|
|
926
|
+
id: S.String,
|
|
927
|
+
activeRequest: S.NullOr(S.String),
|
|
928
|
+
articleCount: S.Number
|
|
929
|
+
}),
|
|
930
|
+
S.Struct({
|
|
931
|
+
_tag: S.Literal("cancelled"),
|
|
932
|
+
id: S.String,
|
|
933
|
+
articleCount: S.Number
|
|
934
|
+
})
|
|
935
|
+
]),
|
|
936
|
+
computed({
|
|
937
|
+
articleCount: relation<DomainEnc>("items").count()
|
|
938
|
+
})
|
|
939
|
+
)
|
|
940
|
+
)
|
|
941
|
+
|
|
942
|
+
make<DomainEnc>().pipe(
|
|
943
|
+
projectComputed(
|
|
944
|
+
S.Union([
|
|
945
|
+
S.Struct({
|
|
946
|
+
_tag: S.Literal("packing"),
|
|
947
|
+
id: S.String,
|
|
948
|
+
activeRequest: S.NullOr(S.String),
|
|
949
|
+
articleCount: S.Number
|
|
950
|
+
}),
|
|
951
|
+
S.Struct({
|
|
952
|
+
_tag: S.Literal("cancelled"),
|
|
953
|
+
id: S.String,
|
|
954
|
+
activeRequest: S.NullOr(S.String),
|
|
955
|
+
articleCount: S.Number
|
|
956
|
+
})
|
|
957
|
+
]),
|
|
958
|
+
// @ts-expect-error activeRequest is not on domain cancelled — cannot project it there
|
|
959
|
+
computed({
|
|
960
|
+
articleCount: relation<DomainEnc>("items").count()
|
|
961
|
+
})
|
|
962
|
+
)
|
|
963
|
+
)
|
|
964
|
+
})
|
|
965
|
+
|
|
966
|
+
it("ProjectableFromDomain distributes over tagged union Encoded", () => {
|
|
967
|
+
type DomainEnc =
|
|
968
|
+
| { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
|
|
969
|
+
| { readonly _tag: "cancelled"; readonly id: string }
|
|
970
|
+
|
|
971
|
+
type Good =
|
|
972
|
+
| { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null; readonly n: number }
|
|
973
|
+
| { readonly _tag: "cancelled"; readonly id: string; readonly n: number }
|
|
974
|
+
|
|
975
|
+
type Bad =
|
|
976
|
+
| { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
|
|
977
|
+
| { readonly _tag: "cancelled"; readonly id: string; readonly activeRequest: string | null }
|
|
978
|
+
|
|
979
|
+
type GoodCheck = ProjectableFromDomain<Good, DomainEnc, "n">
|
|
980
|
+
type BadCheck = ProjectableFromDomain<Bad, DomainEnc>
|
|
981
|
+
|
|
982
|
+
const _good: GoodCheck = undefined
|
|
983
|
+
// @ts-expect-error cancelled branch requires activeRequest not present on domain cancelled
|
|
984
|
+
const _bad: BadCheck = undefined as unknown
|
|
985
|
+
void _good
|
|
986
|
+
void _bad
|
|
987
|
+
})
|
|
988
|
+
|
|
989
|
+
it("ProjectableFromDomain allows dual same-tag domain variants", () => {
|
|
990
|
+
// EasyLife packing/closed: two domain shapes share `_tag` with different fields.
|
|
991
|
+
type DomainEnc =
|
|
992
|
+
| { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
|
|
993
|
+
| { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
|
|
994
|
+
| { readonly _tag: "initial"; readonly id: string }
|
|
995
|
+
|
|
996
|
+
type Good =
|
|
997
|
+
| { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
|
|
998
|
+
| { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
|
|
999
|
+
|
|
1000
|
+
// Flat multi-tag + state-only field must still fail (batchId not on initial).
|
|
1001
|
+
type BadFlat = {
|
|
1002
|
+
readonly _tag: "packing" | "initial"
|
|
1003
|
+
readonly id: string
|
|
1004
|
+
readonly packages: readonly string[]
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
type GoodCheck = ProjectableFromDomain<Good, DomainEnc>
|
|
1008
|
+
type BadFlatCheck = ProjectableFromDomain<BadFlat, DomainEnc>
|
|
1009
|
+
|
|
1010
|
+
const _good: GoodCheck = undefined
|
|
1011
|
+
// @ts-expect-error packages is not on domain initial; multi-tag flat intersection rejects it
|
|
1012
|
+
const _badFlat: BadFlatCheck = undefined as unknown
|
|
1013
|
+
void _good
|
|
1014
|
+
void _badFlat
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
it("projection schema with computed fields fails without computed map", () => {
|
|
1018
|
+
const baseSchema = S.Struct({
|
|
1019
|
+
id: S.String,
|
|
1020
|
+
items: S.Array(S.Struct({ value: S.Number }))
|
|
1021
|
+
})
|
|
1022
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1023
|
+
// @ts-expect-error missing computed keys are rejected; also not projectable from domain alone
|
|
1024
|
+
projectComputed(S.Struct({ pickedCount: S.NonNegativeInt }), computed({}))
|
|
1025
|
+
)
|
|
1026
|
+
expect(() => toFilter(query, baseSchema)).toThrowError("Missing computed projections for schema keys")
|
|
1027
|
+
})
|
|
1028
|
+
|
|
1029
|
+
it("projectComputed.every emits relation-every IR", () => {
|
|
1030
|
+
const baseSchema = S.Struct({
|
|
1031
|
+
id: S.String,
|
|
1032
|
+
items: S.Array(S.Struct({ state: S.Struct({ _tag: S.String }) }))
|
|
1033
|
+
})
|
|
1034
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1035
|
+
projectComputed(
|
|
1036
|
+
S.Struct({ allPicked: S.Boolean }),
|
|
1037
|
+
computed({
|
|
1038
|
+
allPicked: relation<S.Codec.Encoded<typeof baseSchema>>("items").every(where("state._tag", "Picked"))
|
|
1039
|
+
})
|
|
1040
|
+
)
|
|
1041
|
+
)
|
|
1042
|
+
const interpreted = toFilter(query, baseSchema)
|
|
1043
|
+
expect(interpreted.computed?.["allPicked"]?._tag).toBe("relation-every")
|
|
1044
|
+
expect(interpreted.computed?.["allPicked"]?.path).toBe("items")
|
|
1045
|
+
expect((interpreted.computed?.["allPicked"] as { filter: unknown } | undefined)?.filter).toEqual([
|
|
1046
|
+
{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
|
|
1047
|
+
])
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
it("projectComputed.distinctCount emits relation-distinct-count IR with field", () => {
|
|
1051
|
+
const baseSchema = S.Struct({
|
|
1052
|
+
id: S.String,
|
|
1053
|
+
items: S.Array(S.Struct({ rowId: S.String, state: S.Struct({ _tag: S.String }) }))
|
|
1054
|
+
})
|
|
1055
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1056
|
+
projectComputed(
|
|
1057
|
+
S.Struct({ positionCount: S.NonNegativeInt }),
|
|
1058
|
+
computed({
|
|
1059
|
+
positionCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").distinctCount(
|
|
1060
|
+
"rowId",
|
|
1061
|
+
where("state._tag", "neq", "cancelled")
|
|
1062
|
+
)
|
|
1063
|
+
})
|
|
1064
|
+
)
|
|
1065
|
+
)
|
|
1066
|
+
const interpreted = toFilter(query, baseSchema)
|
|
1067
|
+
const ir = interpreted.computed?.["positionCount"]
|
|
1068
|
+
expect(ir?._tag).toBe("relation-distinct-count")
|
|
1069
|
+
expect((ir as { field: string } | undefined)?.field).toBe("rowId")
|
|
1070
|
+
expect((ir as { filter: unknown } | undefined)?.filter).toEqual([
|
|
1071
|
+
{ t: "where", path: "items.-1.state._tag", op: "neq", value: "cancelled" }
|
|
1072
|
+
])
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
it("projectComputed.sum emits relation-sum IR with field", () => {
|
|
1076
|
+
const baseSchema = S.Struct({
|
|
1077
|
+
id: S.String,
|
|
1078
|
+
items: S.Array(S.Struct({ weight: S.Number }))
|
|
1079
|
+
})
|
|
1080
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1081
|
+
projectComputed(
|
|
1082
|
+
S.Struct({ totalWeight: S.Number }),
|
|
1083
|
+
computed({ totalWeight: relation<S.Codec.Encoded<typeof baseSchema>>("items").sum("weight") })
|
|
1084
|
+
)
|
|
1085
|
+
)
|
|
1086
|
+
const interpreted = toFilter(query, baseSchema)
|
|
1087
|
+
const ir = interpreted.computed?.["totalWeight"]
|
|
1088
|
+
expect(ir?._tag).toBe("relation-sum")
|
|
1089
|
+
expect((ir as { field: string } | undefined)?.field).toBe("weight")
|
|
1090
|
+
expect((ir as { filter: unknown } | undefined)?.filter).toEqual([])
|
|
1091
|
+
})
|
|
1092
|
+
|
|
1093
|
+
it("projectComputed.collect / collectDistinct emit relation-collect IR", () => {
|
|
1094
|
+
const baseSchema = S.Struct({
|
|
1095
|
+
id: S.String,
|
|
1096
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
1097
|
+
})
|
|
1098
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1099
|
+
projectComputed(
|
|
1100
|
+
S.Struct({
|
|
1101
|
+
all: S.Array(S.String),
|
|
1102
|
+
distinct: S.Array(S.String)
|
|
1103
|
+
}),
|
|
1104
|
+
computed({
|
|
1105
|
+
all: relation<S.Codec.Encoded<typeof baseSchema>>("items").collect("articleId"),
|
|
1106
|
+
distinct: relation<S.Codec.Encoded<typeof baseSchema>>("items").collectDistinct("articleId")
|
|
1107
|
+
})
|
|
1108
|
+
)
|
|
1109
|
+
)
|
|
1110
|
+
const interpreted = toFilter(query, baseSchema)
|
|
1111
|
+
const all = interpreted.computed?.["all"]
|
|
1112
|
+
const distinct = interpreted.computed?.["distinct"]
|
|
1113
|
+
expect(all?._tag).toBe("relation-collect")
|
|
1114
|
+
expect((all as { distinct: boolean } | undefined)?.distinct).toBe(false)
|
|
1115
|
+
expect(distinct?._tag).toBe("relation-collect")
|
|
1116
|
+
expect((distinct as { distinct: boolean } | undefined)?.distinct).toBe(true)
|
|
1117
|
+
})
|
|
1118
|
+
|
|
1119
|
+
it("projectComputed.sumExpr emits relation-sum-expr IR", () => {
|
|
1120
|
+
const baseSchema = S.Struct({
|
|
1121
|
+
id: S.String,
|
|
1122
|
+
items: S.Array(S.Struct({
|
|
1123
|
+
weight: S.Number,
|
|
1124
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
1125
|
+
}))
|
|
1126
|
+
})
|
|
1127
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1128
|
+
projectComputed(
|
|
1129
|
+
S.Struct({ total: S.Number }),
|
|
1130
|
+
computed({
|
|
1131
|
+
total: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExpr(
|
|
1132
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
1133
|
+
)
|
|
1134
|
+
})
|
|
1135
|
+
)
|
|
1136
|
+
)
|
|
1137
|
+
const interpreted = toFilter(query, baseSchema)
|
|
1138
|
+
const ir = interpreted.computed?.["total"]
|
|
1139
|
+
expect(ir?._tag).toBe("relation-sum-expr")
|
|
1140
|
+
expect((ir as { expression: unknown } | undefined)?.expression).toEqual({
|
|
1141
|
+
_tag: "mul",
|
|
1142
|
+
left: { _tag: "field", field: "weight" },
|
|
1143
|
+
right: { _tag: "field", field: "tradeUnit.amount" }
|
|
1144
|
+
})
|
|
1145
|
+
})
|
|
1146
|
+
|
|
1147
|
+
it("projectComputed.sumExprBy emits relation-sum-expr-by IR", () => {
|
|
1148
|
+
const baseSchema = S.Struct({
|
|
1149
|
+
id: S.String,
|
|
1150
|
+
items: S.Array(S.Struct({
|
|
1151
|
+
weight: S.Number,
|
|
1152
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
1153
|
+
}))
|
|
1154
|
+
})
|
|
1155
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1156
|
+
projectComputed(
|
|
1157
|
+
S.Struct({
|
|
1158
|
+
totals: S.Array(S.Struct({ unit: S.String, total: S.Number }))
|
|
1159
|
+
}),
|
|
1160
|
+
computed({
|
|
1161
|
+
totals: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExprBy(
|
|
1162
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount")),
|
|
1163
|
+
{ unit: "tradeUnit.unit" }
|
|
1164
|
+
)
|
|
1165
|
+
})
|
|
1166
|
+
)
|
|
1167
|
+
)
|
|
1168
|
+
const interpreted = toFilter(query, baseSchema)
|
|
1169
|
+
const ir = interpreted.computed?.["totals"]
|
|
1170
|
+
expect(ir?._tag).toBe("relation-sum-expr-by")
|
|
1171
|
+
expect((ir as { unit: string } | undefined)?.unit).toBe("tradeUnit.unit")
|
|
1172
|
+
})
|
|
1173
|
+
|
|
548
1174
|
it(
|
|
549
1175
|
"doesn't mess when refining fields",
|
|
550
1176
|
() =>
|
|
@@ -572,7 +1198,7 @@ it(
|
|
|
572
1198
|
|
|
573
1199
|
expect(result).toEqual([])
|
|
574
1200
|
})
|
|
575
|
-
.pipe(Effect.provide(
|
|
1201
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
576
1202
|
)
|
|
577
1203
|
|
|
578
1204
|
it(
|
|
@@ -616,7 +1242,7 @@ it(
|
|
|
616
1242
|
|
|
617
1243
|
expect(result).toEqual([])
|
|
618
1244
|
})
|
|
619
|
-
.pipe(Effect.provide(
|
|
1245
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
620
1246
|
)
|
|
621
1247
|
|
|
622
1248
|
it(
|
|
@@ -660,7 +1286,7 @@ it(
|
|
|
660
1286
|
|
|
661
1287
|
expect(result).toEqual([])
|
|
662
1288
|
})
|
|
663
|
-
.pipe(Effect.provide(
|
|
1289
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
664
1290
|
)
|
|
665
1291
|
|
|
666
1292
|
it("remove null from one constituent of a tagged union", () =>
|
|
@@ -709,7 +1335,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
709
1335
|
})[]
|
|
710
1336
|
>()
|
|
711
1337
|
})
|
|
712
|
-
.pipe(Effect.provide(
|
|
1338
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
713
1339
|
|
|
714
1340
|
it("refine 3", () =>
|
|
715
1341
|
Effect
|
|
@@ -747,7 +1373,7 @@ it("refine 3", () =>
|
|
|
747
1373
|
const resQuer1 = yield* repo.query(where("id", "AA"))
|
|
748
1374
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
749
1375
|
})
|
|
750
|
-
.pipe(Effect.provide(
|
|
1376
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
751
1377
|
|
|
752
1378
|
it("my test", () =>
|
|
753
1379
|
Effect
|
|
@@ -765,7 +1391,7 @@ it("my test", () =>
|
|
|
765
1391
|
)
|
|
766
1392
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
767
1393
|
})
|
|
768
|
-
.pipe(Effect.provide(
|
|
1394
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
769
1395
|
|
|
770
1396
|
it("refine inner without imposing a projection", () =>
|
|
771
1397
|
Effect
|
|
@@ -840,7 +1466,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
840
1466
|
}[]
|
|
841
1467
|
>()
|
|
842
1468
|
})
|
|
843
|
-
.pipe(Effect.provide(
|
|
1469
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
844
1470
|
|
|
845
1471
|
it("does not allow string queries on arrays", () =>
|
|
846
1472
|
Effect
|
|
@@ -874,8 +1500,51 @@ it("does not allow string queries on arrays", () =>
|
|
|
874
1500
|
expectTypeOf(good2).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
875
1501
|
expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
876
1502
|
expectTypeOf(good4).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
1503
|
+
|
|
1504
|
+
type Native = {
|
|
1505
|
+
readonly id: string
|
|
1506
|
+
readonly dates: Date[]
|
|
1507
|
+
readonly dateSet: ReadonlySet<Date>
|
|
1508
|
+
readonly tags: ReadonlySet<string>
|
|
1509
|
+
}
|
|
1510
|
+
const native = make<Native>()
|
|
1511
|
+
const d = new Date("2020-01-01T00:00:00.000Z")
|
|
1512
|
+
const n1 = native.pipe(where("dates", "includes", d))
|
|
1513
|
+
const n2 = native.pipe(where("dateSet", "includes", d))
|
|
1514
|
+
const n3 = native.pipe(where("tags", "includes", "a"))
|
|
1515
|
+
const n4 = native.pipe(where("dates", "includes-any", [d]))
|
|
1516
|
+
const n5 = native.pipe(where("dateSet", "includes-any", new Set([d])))
|
|
1517
|
+
const n6 = native.pipe(where("id", "in", new Set(["x"])))
|
|
1518
|
+
expectTypeOf(n1).toEqualTypeOf<QueryWhere<Native, Native>>()
|
|
1519
|
+
expectTypeOf(n2).toEqualTypeOf<QueryWhere<Native, Native>>()
|
|
1520
|
+
expectTypeOf(n3).toEqualTypeOf<QueryWhere<Native, Native>>()
|
|
1521
|
+
expectTypeOf(n4).toEqualTypeOf<QueryWhere<Native, Native>>()
|
|
1522
|
+
expectTypeOf(n5).toEqualTypeOf<QueryWhere<Native, Native>>()
|
|
1523
|
+
expectTypeOf(n6).toEqualTypeOf<QueryWhere<Native, Native>>()
|
|
1524
|
+
|
|
1525
|
+
type WithMap = {
|
|
1526
|
+
readonly id: string
|
|
1527
|
+
readonly meta: ReadonlyMap<string, number>
|
|
1528
|
+
}
|
|
1529
|
+
const mapped = make<WithMap>()
|
|
1530
|
+
const m1 = mapped.pipe(where("meta", "hasKey", "n"))
|
|
1531
|
+
const m2 = mapped.pipe(where("meta", "hasValue", 1))
|
|
1532
|
+
const m3 = mapped.pipe(where("meta", "hasKeyValue", ["n", 1] as const))
|
|
1533
|
+
const m4 = mapped.pipe(where("meta", "hasKey-any", ["n", "x"]))
|
|
1534
|
+
const m5 = mapped.pipe(where("meta", "hasValue-all", new Set([1, 2])))
|
|
1535
|
+
const m6 = mapped.pipe(where("meta", "hasKeyValue-any", [["n", 1] as const, ["x", 2] as const]))
|
|
1536
|
+
expectTypeOf(m1).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
|
|
1537
|
+
expectTypeOf(m2).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
|
|
1538
|
+
expectTypeOf(m3).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
|
|
1539
|
+
expectTypeOf(m4).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
|
|
1540
|
+
expectTypeOf(m5).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
|
|
1541
|
+
expectTypeOf(m6).toEqualTypeOf<QueryWhere<WithMap, WithMap>>()
|
|
1542
|
+
// @ts-expect-error cannot hasKey on a string field
|
|
1543
|
+
mapped.pipe(where("id", "hasKey", "n"))
|
|
1544
|
+
// @ts-expect-error hasKey value must be the map key type
|
|
1545
|
+
mapped.pipe(where("meta", "hasKey", 1))
|
|
877
1546
|
})
|
|
878
|
-
.pipe(Effect.provide(
|
|
1547
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
879
1548
|
|
|
880
1549
|
it("test array.length", () =>
|
|
881
1550
|
Effect
|
|
@@ -916,7 +1585,7 @@ it("test array.length", () =>
|
|
|
916
1585
|
QueryWhere<Something, Something>
|
|
917
1586
|
>()
|
|
918
1587
|
})
|
|
919
|
-
.pipe(Effect.provide(
|
|
1588
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
920
1589
|
|
|
921
1590
|
it("distribution over union", () =>
|
|
922
1591
|
Effect
|
|
@@ -940,7 +1609,7 @@ it("distribution over union", () =>
|
|
|
940
1609
|
})[]
|
|
941
1610
|
>()
|
|
942
1611
|
})
|
|
943
|
-
.pipe(Effect.provide(
|
|
1612
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
944
1613
|
|
|
945
1614
|
it("refine nested union", () =>
|
|
946
1615
|
Effect
|
|
@@ -981,7 +1650,7 @@ it("refine nested union", () =>
|
|
|
981
1650
|
}[]
|
|
982
1651
|
>()
|
|
983
1652
|
})
|
|
984
|
-
.pipe(Effect.provide(
|
|
1653
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
985
1654
|
|
|
986
1655
|
it("find with transformed id", () =>
|
|
987
1656
|
Effect
|
|
@@ -996,11 +1665,13 @@ it("find with transformed id", () =>
|
|
|
996
1665
|
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
997
1666
|
S.decodeTo(
|
|
998
1667
|
S.toType(PreconfigurationId),
|
|
999
|
-
SchemaTransformation.
|
|
1668
|
+
SchemaTransformation.transformEffect({
|
|
1000
1669
|
decode: Effect.fnUntraced(function*(value) {
|
|
1001
1670
|
const values = value.split("_")
|
|
1002
1671
|
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1003
|
-
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1672
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1673
|
+
values.join("_")
|
|
1674
|
+
)
|
|
1004
1675
|
return new PreconfigurationId({ configuratorId, label })
|
|
1005
1676
|
}),
|
|
1006
1677
|
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
@@ -1037,7 +1708,7 @@ it("find with transformed id", () =>
|
|
|
1037
1708
|
)
|
|
1038
1709
|
expect(Option.isNone(notFound)).toBe(true)
|
|
1039
1710
|
})
|
|
1040
|
-
.pipe(Effect.provide(
|
|
1711
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1041
1712
|
|
|
1042
1713
|
it("find with transformed id in tagged union", () =>
|
|
1043
1714
|
Effect
|
|
@@ -1052,11 +1723,13 @@ it("find with transformed id in tagged union", () =>
|
|
|
1052
1723
|
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1053
1724
|
S.decodeTo(
|
|
1054
1725
|
S.toType(PreconfigurationId),
|
|
1055
|
-
SchemaTransformation.
|
|
1726
|
+
SchemaTransformation.transformEffect({
|
|
1056
1727
|
decode: Effect.fnUntraced(function*(value) {
|
|
1057
1728
|
const values = value.split("_")
|
|
1058
1729
|
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1059
|
-
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1730
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1731
|
+
values.join("_")
|
|
1732
|
+
)
|
|
1060
1733
|
return new PreconfigurationId({ configuratorId, label })
|
|
1061
1734
|
}),
|
|
1062
1735
|
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
@@ -1128,7 +1801,7 @@ it("find with transformed id in tagged union", () =>
|
|
|
1128
1801
|
)
|
|
1129
1802
|
expect(Option.isNone(notFound)).toBe(true)
|
|
1130
1803
|
})
|
|
1131
|
-
.pipe(Effect.provide(
|
|
1804
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1132
1805
|
|
|
1133
1806
|
it("refine union with nested union", () =>
|
|
1134
1807
|
Effect
|
|
@@ -1243,4 +1916,521 @@ it("refine union with nested union", () =>
|
|
|
1243
1916
|
})[]
|
|
1244
1917
|
>()
|
|
1245
1918
|
})
|
|
1246
|
-
.pipe(Effect.provide(
|
|
1919
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1920
|
+
|
|
1921
|
+
// ---------------------------------------------------------------------------
|
|
1922
|
+
// memFilter: computed projection execution (in-memory) and code filter coverage
|
|
1923
|
+
// ---------------------------------------------------------------------------
|
|
1924
|
+
|
|
1925
|
+
const computedBaseSchema = S.Struct({
|
|
1926
|
+
id: S.String,
|
|
1927
|
+
status: S.Literals(["active", "archived"]),
|
|
1928
|
+
items: S.Array(S.Struct({
|
|
1929
|
+
id: S.String,
|
|
1930
|
+
tag: S.Literals(["a", "b", "c"]),
|
|
1931
|
+
qty: S.Finite,
|
|
1932
|
+
note: S.String
|
|
1933
|
+
}))
|
|
1934
|
+
})
|
|
1935
|
+
type ComputedBase = S.Codec.Encoded<typeof computedBaseSchema>
|
|
1936
|
+
|
|
1937
|
+
const computedRows: ComputedBase[] = [
|
|
1938
|
+
{
|
|
1939
|
+
id: "r1",
|
|
1940
|
+
status: "active",
|
|
1941
|
+
items: [
|
|
1942
|
+
{ id: "i1", tag: "a", qty: 10, note: "alpha" },
|
|
1943
|
+
{ id: "i2", tag: "a", qty: 20, note: "alpha" },
|
|
1944
|
+
{ id: "i3", tag: "b", qty: 5, note: "beta" }
|
|
1945
|
+
]
|
|
1946
|
+
},
|
|
1947
|
+
{ id: "r2", status: "active", items: [] },
|
|
1948
|
+
{
|
|
1949
|
+
id: "r3",
|
|
1950
|
+
status: "archived",
|
|
1951
|
+
items: [
|
|
1952
|
+
{ id: "i4", tag: "b", qty: 7, note: "gamma" },
|
|
1953
|
+
{ id: "i5", tag: "b", qty: 7, note: "gamma" },
|
|
1954
|
+
{ id: "i6", tag: "c", qty: 3, note: "delta" }
|
|
1955
|
+
]
|
|
1956
|
+
}
|
|
1957
|
+
]
|
|
1958
|
+
|
|
1959
|
+
it("memFilter: relation-count with filter", () => {
|
|
1960
|
+
const q = make<ComputedBase>().pipe(
|
|
1961
|
+
projectComputed(
|
|
1962
|
+
S.Struct({ id: S.String, aCount: S.NonNegativeInt }),
|
|
1963
|
+
computed({
|
|
1964
|
+
aCount: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
1965
|
+
})
|
|
1966
|
+
)
|
|
1967
|
+
)
|
|
1968
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1969
|
+
{ id: "r1", aCount: 2 },
|
|
1970
|
+
{ id: "r2", aCount: 0 },
|
|
1971
|
+
{ id: "r3", aCount: 0 }
|
|
1972
|
+
])
|
|
1973
|
+
})
|
|
1974
|
+
|
|
1975
|
+
it("memFilter: relation-any / every with filter", () => {
|
|
1976
|
+
const q = make<ComputedBase>().pipe(
|
|
1977
|
+
projectComputed(
|
|
1978
|
+
S.Struct({
|
|
1979
|
+
id: S.String,
|
|
1980
|
+
hasA: S.Boolean,
|
|
1981
|
+
allB: S.Boolean
|
|
1982
|
+
}),
|
|
1983
|
+
computed({
|
|
1984
|
+
hasA: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
1985
|
+
allB: relation<ComputedBase>("items").every(where("tag", "b"))
|
|
1986
|
+
})
|
|
1987
|
+
)
|
|
1988
|
+
)
|
|
1989
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1990
|
+
{ id: "r1", hasA: true, allB: false },
|
|
1991
|
+
// empty array: any → false, every → true (JS Array.every on [] is true)
|
|
1992
|
+
{ id: "r2", hasA: false, allB: true },
|
|
1993
|
+
{ id: "r3", hasA: false, allB: false }
|
|
1994
|
+
])
|
|
1995
|
+
})
|
|
1996
|
+
|
|
1997
|
+
it("memFilter: relation-distinct-count with filter", () => {
|
|
1998
|
+
const q = make<ComputedBase>().pipe(
|
|
1999
|
+
projectComputed(
|
|
2000
|
+
S.Struct({ id: S.String, distinctNotes: S.NonNegativeInt }),
|
|
2001
|
+
computed({
|
|
2002
|
+
distinctNotes: relation<ComputedBase>("items").distinctCount("note", where("tag", "neq", "c"))
|
|
2003
|
+
})
|
|
2004
|
+
)
|
|
2005
|
+
)
|
|
2006
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
2007
|
+
{ id: "r1", distinctNotes: 2 }, // alpha, beta
|
|
2008
|
+
{ id: "r2", distinctNotes: 0 },
|
|
2009
|
+
{ id: "r3", distinctNotes: 1 } // gamma (delta filtered out)
|
|
2010
|
+
])
|
|
2011
|
+
})
|
|
2012
|
+
|
|
2013
|
+
it("memFilter: relation-sum with filter", () => {
|
|
2014
|
+
const q = make<ComputedBase>().pipe(
|
|
2015
|
+
projectComputed(
|
|
2016
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
2017
|
+
computed({
|
|
2018
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
2019
|
+
})
|
|
2020
|
+
)
|
|
2021
|
+
)
|
|
2022
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
2023
|
+
{ id: "r1", totalQty: 35 },
|
|
2024
|
+
{ id: "r2", totalQty: 0 },
|
|
2025
|
+
{ id: "r3", totalQty: 14 }
|
|
2026
|
+
])
|
|
2027
|
+
})
|
|
2028
|
+
|
|
2029
|
+
it("memFilter: relation-collect / collectDistinct with filter", () => {
|
|
2030
|
+
const q = make<ComputedBase>().pipe(
|
|
2031
|
+
projectComputed(
|
|
2032
|
+
S.Struct({
|
|
2033
|
+
id: S.String,
|
|
2034
|
+
notes: S.Array(S.String),
|
|
2035
|
+
distinctNotes: S.Array(S.String)
|
|
2036
|
+
}),
|
|
2037
|
+
computed({
|
|
2038
|
+
notes: relation<ComputedBase>("items").collect("note", where("tag", "neq", "c")),
|
|
2039
|
+
distinctNotes: relation<ComputedBase>("items").collectDistinct("note", where("tag", "neq", "c"))
|
|
2040
|
+
})
|
|
2041
|
+
)
|
|
2042
|
+
)
|
|
2043
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
2044
|
+
{ id: "r1", notes: ["alpha", "alpha", "beta"], distinctNotes: ["alpha", "beta"] },
|
|
2045
|
+
{ id: "r2", notes: [], distinctNotes: [] },
|
|
2046
|
+
{ id: "r3", notes: ["gamma", "gamma"], distinctNotes: ["gamma"] }
|
|
2047
|
+
])
|
|
2048
|
+
})
|
|
2049
|
+
|
|
2050
|
+
it("memFilter: computed projection with multi-statement relation filter", () => {
|
|
2051
|
+
const q = make<ComputedBase>().pipe(
|
|
2052
|
+
projectComputed(
|
|
2053
|
+
S.Struct({ id: S.String, hits: S.NonNegativeInt }),
|
|
2054
|
+
computed({
|
|
2055
|
+
hits: relation<ComputedBase>("items").count(
|
|
2056
|
+
flow(
|
|
2057
|
+
where("tag", "a"),
|
|
2058
|
+
and("qty", "gt", 10)
|
|
2059
|
+
)
|
|
2060
|
+
)
|
|
2061
|
+
})
|
|
2062
|
+
)
|
|
2063
|
+
)
|
|
2064
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
2065
|
+
{ id: "r1", hits: 1 }, // only i2 (a, qty 20)
|
|
2066
|
+
{ id: "r2", hits: 0 },
|
|
2067
|
+
{ id: "r3", hits: 0 }
|
|
2068
|
+
])
|
|
2069
|
+
})
|
|
2070
|
+
|
|
2071
|
+
it("memFilter: relation-sum-expr / sum-expr-by / sum-expr-normalized", () => {
|
|
2072
|
+
const schema = S.Struct({
|
|
2073
|
+
id: S.String,
|
|
2074
|
+
items: S.Array(S.Struct({
|
|
2075
|
+
weight: S.Finite,
|
|
2076
|
+
tradeUnit: S.Struct({ amount: S.Finite, unit: S.String })
|
|
2077
|
+
}))
|
|
2078
|
+
})
|
|
2079
|
+
type Row = S.Codec.Encoded<typeof schema>
|
|
2080
|
+
const rows: Row[] = [
|
|
2081
|
+
{
|
|
2082
|
+
id: "r1",
|
|
2083
|
+
items: [
|
|
2084
|
+
{ weight: 2, tradeUnit: { amount: 5, unit: "kg" } },
|
|
2085
|
+
{ weight: 4, tradeUnit: { amount: 1000, unit: "g" } },
|
|
2086
|
+
{ weight: 3, tradeUnit: { amount: 1, unit: "kg" } }
|
|
2087
|
+
]
|
|
2088
|
+
},
|
|
2089
|
+
{ id: "r2", items: [] }
|
|
2090
|
+
]
|
|
2091
|
+
const weighted = expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
2092
|
+
const q = make<Row>().pipe(
|
|
2093
|
+
projectComputed(
|
|
2094
|
+
S.Struct({
|
|
2095
|
+
id: S.String,
|
|
2096
|
+
totalRaw: S.Finite,
|
|
2097
|
+
totalsByUnit: S.Array(S.Struct({ unit: S.String, total: S.Finite })),
|
|
2098
|
+
totalKg: S.Finite
|
|
2099
|
+
}),
|
|
2100
|
+
computed({
|
|
2101
|
+
totalRaw: relation<Row>("items").sumExpr(weighted, where("weight", "gte", 0)),
|
|
2102
|
+
totalsByUnit: relation<Row>("items").sumExprBy(weighted, { unit: "tradeUnit.unit" }, where("weight", "gte", 0)),
|
|
2103
|
+
totalKg: relation<Row>("items").sumExprNormalized(weighted, {
|
|
2104
|
+
unit: "tradeUnit.unit",
|
|
2105
|
+
toBase: "kg",
|
|
2106
|
+
factors: { g: 0.001 }
|
|
2107
|
+
}, where("weight", "gte", 0))
|
|
2108
|
+
})
|
|
2109
|
+
)
|
|
2110
|
+
)
|
|
2111
|
+
expect(memFilter(toFilter(q, schema))(rows)).toEqual([
|
|
2112
|
+
{
|
|
2113
|
+
id: "r1",
|
|
2114
|
+
totalRaw: 4013,
|
|
2115
|
+
totalsByUnit: [{ unit: "kg", total: 13 }, { unit: "g", total: 4000 }],
|
|
2116
|
+
totalKg: 17
|
|
2117
|
+
},
|
|
2118
|
+
{ id: "r2", totalRaw: 0, totalsByUnit: [], totalKg: 0 }
|
|
2119
|
+
])
|
|
2120
|
+
})
|
|
2121
|
+
|
|
2122
|
+
it("memFilter: computed projection combined with root where filter", () => {
|
|
2123
|
+
const q = make<ComputedBase>().pipe(
|
|
2124
|
+
where("id", "neq", "r3"),
|
|
2125
|
+
projectComputed(
|
|
2126
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
2127
|
+
computed({
|
|
2128
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
2129
|
+
})
|
|
2130
|
+
)
|
|
2131
|
+
)
|
|
2132
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
2133
|
+
{ id: "r1", totalQty: 35 },
|
|
2134
|
+
{ id: "r2", totalQty: 0 }
|
|
2135
|
+
])
|
|
2136
|
+
})
|
|
2137
|
+
|
|
2138
|
+
it("memFilter: computed projection with order/limit/skip applied to base rows", () => {
|
|
2139
|
+
const q = make<ComputedBase>().pipe(
|
|
2140
|
+
order("id", "DESC"),
|
|
2141
|
+
page({ skip: 1, take: 1 }),
|
|
2142
|
+
projectComputed(
|
|
2143
|
+
S.Struct({ id: S.String, total: S.NonNegativeInt }),
|
|
2144
|
+
computed({
|
|
2145
|
+
total: relation<ComputedBase>("items").count(where("qty", "gte", 0))
|
|
2146
|
+
})
|
|
2147
|
+
)
|
|
2148
|
+
)
|
|
2149
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
2150
|
+
{ id: "r2", total: 0 }
|
|
2151
|
+
])
|
|
2152
|
+
})
|
|
2153
|
+
|
|
2154
|
+
it("memFilter: computed projection - relation missing on row returns empty value", () => {
|
|
2155
|
+
const partial: ComputedBase[] = [
|
|
2156
|
+
{ id: "x1" } as ComputedBase,
|
|
2157
|
+
{ id: "x2", status: "active", items: undefined as unknown as ComputedBase["items"] }
|
|
2158
|
+
]
|
|
2159
|
+
const q = make<ComputedBase>().pipe(
|
|
2160
|
+
projectComputed(
|
|
2161
|
+
S.Struct({
|
|
2162
|
+
id: S.String,
|
|
2163
|
+
c: S.NonNegativeInt,
|
|
2164
|
+
s: S.Finite,
|
|
2165
|
+
any_: S.Boolean,
|
|
2166
|
+
every_: S.Boolean,
|
|
2167
|
+
coll: S.Array(S.String)
|
|
2168
|
+
}),
|
|
2169
|
+
computed({
|
|
2170
|
+
c: relation<ComputedBase>("items").count(where("tag", "a")),
|
|
2171
|
+
s: relation<ComputedBase>("items").sum("qty", where("tag", "a")),
|
|
2172
|
+
any_: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
2173
|
+
every_: relation<ComputedBase>("items").every(where("tag", "a")),
|
|
2174
|
+
coll: relation<ComputedBase>("items").collect("note", where("tag", "a"))
|
|
2175
|
+
})
|
|
2176
|
+
)
|
|
2177
|
+
)
|
|
2178
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(partial)).toEqual([
|
|
2179
|
+
{ id: "x1", c: 0, s: 0, any_: false, every_: true, coll: [] },
|
|
2180
|
+
{ id: "x2", c: 0, s: 0, any_: false, every_: true, coll: [] }
|
|
2181
|
+
])
|
|
2182
|
+
})
|
|
2183
|
+
|
|
2184
|
+
it("memFilter: rejects extra computed keys not in projection schema", () => {
|
|
2185
|
+
const q = make<ComputedBase>().pipe(
|
|
2186
|
+
projectComputed(
|
|
2187
|
+
S.Struct({ id: S.String }),
|
|
2188
|
+
// @ts-expect-error extra computed keys are rejected statically; keep runtime guard covered
|
|
2189
|
+
computed({
|
|
2190
|
+
bogus: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
2191
|
+
})
|
|
2192
|
+
)
|
|
2193
|
+
)
|
|
2194
|
+
expect(() => toFilter(q, computedBaseSchema)).toThrowError(
|
|
2195
|
+
"Computed projection keys must exist in projection schema"
|
|
2196
|
+
)
|
|
2197
|
+
})
|
|
2198
|
+
|
|
2199
|
+
// ---------------------------------------------------------------------------
|
|
2200
|
+
// memFilter: code filter (where/and/or/scopes) execution coverage
|
|
2201
|
+
// ---------------------------------------------------------------------------
|
|
2202
|
+
|
|
2203
|
+
type CFRow = {
|
|
2204
|
+
readonly id: string
|
|
2205
|
+
readonly tag: "x" | "y" | "z"
|
|
2206
|
+
readonly qty: number
|
|
2207
|
+
readonly desc: string
|
|
2208
|
+
readonly tags: ReadonlyArray<string>
|
|
2209
|
+
readonly nested: { readonly kind: "k1" | "k2"; readonly v: number }
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
const cfRows: CFRow[] = [
|
|
2213
|
+
{ id: "1", tag: "x", qty: 10, desc: "Hello World", tags: ["red", "green"], nested: { kind: "k1", v: 1 } },
|
|
2214
|
+
{ id: "2", tag: "y", qty: 20, desc: "Goodbye", tags: ["blue"], nested: { kind: "k2", v: 5 } },
|
|
2215
|
+
{ id: "3", tag: "z", qty: 0, desc: "Hello again", tags: ["red", "blue", "green"], nested: { kind: "k1", v: 9 } },
|
|
2216
|
+
{ id: "4", tag: "y", qty: 30, desc: "World cup", tags: [], nested: { kind: "k2", v: 0 } }
|
|
2217
|
+
]
|
|
2218
|
+
|
|
2219
|
+
const runCF = (q: any) => (memFilter(toFilter(q))(cfRows) as unknown as readonly CFRow[]).map((_) => _.id)
|
|
2220
|
+
|
|
2221
|
+
it("codeFilter: where + and chain", () => {
|
|
2222
|
+
const q = make<CFRow>().pipe(
|
|
2223
|
+
where("tag", "y"),
|
|
2224
|
+
and("qty", "gt", 25)
|
|
2225
|
+
)
|
|
2226
|
+
expect(runCF(q)).toEqual(["4"])
|
|
2227
|
+
})
|
|
2228
|
+
|
|
2229
|
+
it("codeFilter: where + or chain", () => {
|
|
2230
|
+
const q = make<CFRow>().pipe(
|
|
2231
|
+
where("tag", "x"),
|
|
2232
|
+
or("tag", "z")
|
|
2233
|
+
)
|
|
2234
|
+
expect(runCF(q).sort()).toEqual(["1", "3"])
|
|
2235
|
+
})
|
|
2236
|
+
|
|
2237
|
+
it("codeFilter: nested scope precedence (a AND (b OR c))", () => {
|
|
2238
|
+
const q = make<CFRow>().pipe(
|
|
2239
|
+
where("tag", "y"),
|
|
2240
|
+
and(
|
|
2241
|
+
where("qty", "gt", 25),
|
|
2242
|
+
or("desc", "contains", "good")
|
|
2243
|
+
)
|
|
2244
|
+
)
|
|
2245
|
+
// tag=y AND (qty>25 OR desc contains "good") → row 2 (Goodbye) and row 4 (qty 30)
|
|
2246
|
+
expect(runCF(q).sort()).toEqual(["2", "4"])
|
|
2247
|
+
})
|
|
2248
|
+
|
|
2249
|
+
it("codeFilter: contains/startsWith/endsWith are case-insensitive", () => {
|
|
2250
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "contains", "WORLD"))).sort()).toEqual(["1", "4"])
|
|
2251
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "startsWith", "hello"))).sort()).toEqual(["1", "3"])
|
|
2252
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "endsWith", "AGAIN")))).toEqual(["3"])
|
|
2253
|
+
})
|
|
2254
|
+
|
|
2255
|
+
it("codeFilter: array includes / includes-any / includes-all", () => {
|
|
2256
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes", "red"))).sort()).toEqual(["1", "3"])
|
|
2257
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-any", ["blue", "green"]))).sort()).toEqual([
|
|
2258
|
+
"1",
|
|
2259
|
+
"2",
|
|
2260
|
+
"3"
|
|
2261
|
+
])
|
|
2262
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-all", ["red", "blue"])))).toEqual(["3"])
|
|
2263
|
+
})
|
|
2264
|
+
|
|
2265
|
+
it("codeFilter: Date array / Set includes and in", () => {
|
|
2266
|
+
const d0 = new Date("2020-01-01T00:00:00.000Z")
|
|
2267
|
+
const d1 = new Date("2021-01-01T00:00:00.000Z")
|
|
2268
|
+
type DateRow = {
|
|
2269
|
+
readonly id: string
|
|
2270
|
+
readonly dates: Date[]
|
|
2271
|
+
readonly dateSet: ReadonlySet<Date>
|
|
2272
|
+
readonly tag: string
|
|
2273
|
+
}
|
|
2274
|
+
const rows: DateRow[] = [
|
|
2275
|
+
{ id: "1", dates: [d0], dateSet: new Set([d0]), tag: "a" },
|
|
2276
|
+
{ id: "2", dates: [d1, d0], dateSet: new Set([d1]), tag: "b" }
|
|
2277
|
+
]
|
|
2278
|
+
const run = (q: any) => (memFilter(toFilter(q))(rows) as DateRow[]).map((_) => _.id)
|
|
2279
|
+
expect(run(make<DateRow>().pipe(where("dates", "includes", d0))).sort()).toEqual(["1", "2"])
|
|
2280
|
+
expect(run(make<DateRow>().pipe(where("dates", "includes", d1)))).toEqual(["2"])
|
|
2281
|
+
expect(run(make<DateRow>().pipe(where("dateSet", "includes", d0)))).toEqual(["1"])
|
|
2282
|
+
expect(run(make<DateRow>().pipe(where("dates", "includes-any", [d1])))).toEqual(["2"])
|
|
2283
|
+
expect(run(make<DateRow>().pipe(where("dateSet", "includes-any", new Set([d0, d1])))).sort()).toEqual([
|
|
2284
|
+
"1",
|
|
2285
|
+
"2"
|
|
2286
|
+
])
|
|
2287
|
+
expect(run(make<DateRow>().pipe(where("tag", "in", new Set(["a"]))))).toEqual(["1"])
|
|
2288
|
+
})
|
|
2289
|
+
|
|
2290
|
+
it("codeFilter: Map hasKey / hasValue / hasKeyValue", () => {
|
|
2291
|
+
type MapRow = {
|
|
2292
|
+
readonly id: string
|
|
2293
|
+
readonly meta: ReadonlyMap<string, number>
|
|
2294
|
+
}
|
|
2295
|
+
const rows: MapRow[] = [
|
|
2296
|
+
{ id: "1", meta: new Map([["n", 1], ["x", 2]]) },
|
|
2297
|
+
{ id: "2", meta: new Map([["n", 9]]) },
|
|
2298
|
+
{ id: "3", meta: new Map([["z", 2]]) }
|
|
2299
|
+
]
|
|
2300
|
+
const run = (q: any) => (memFilter(toFilter(q))(rows) as MapRow[]).map((_) => _.id)
|
|
2301
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasKey", "n"))).sort()).toEqual(["1", "2"])
|
|
2302
|
+
expect(run(make<MapRow>().pipe(where("meta", "notHasKey", "n")))).toEqual(["3"])
|
|
2303
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasValue", 2))).sort()).toEqual(["1", "3"])
|
|
2304
|
+
expect(run(make<MapRow>().pipe(where("meta", "notHasValue", 2)))).toEqual(["2"])
|
|
2305
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasKeyValue", ["n", 1])))).toEqual(["1"])
|
|
2306
|
+
expect(run(make<MapRow>().pipe(where("meta", "notHasKeyValue", ["n", 1]))).sort()).toEqual(["2", "3"])
|
|
2307
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasKey-any", ["z", "missing"])))).toEqual(["3"])
|
|
2308
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasKey-all", ["n", "x"])))).toEqual(["1"])
|
|
2309
|
+
expect(run(make<MapRow>().pipe(where("meta", "notHasKey-all", ["n", "x"]))).sort()).toEqual(["2", "3"])
|
|
2310
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasValue-any", [9, 99])))).toEqual(["2"])
|
|
2311
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasValue-all", [1, 2])))).toEqual(["1"])
|
|
2312
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasKeyValue-any", [["n", 9], ["missing", 0]])))).toEqual(["2"])
|
|
2313
|
+
expect(run(make<MapRow>().pipe(where("meta", "hasKeyValue-all", [["n", 1], ["x", 2]])))).toEqual(["1"])
|
|
2314
|
+
})
|
|
2315
|
+
|
|
2316
|
+
it("codeFilter: in / notIn", () => {
|
|
2317
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "in", ["x", "z"]))).sort()).toEqual(["1", "3"])
|
|
2318
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "notIn", ["x", "z"]))).sort()).toEqual(["2", "4"])
|
|
2319
|
+
})
|
|
2320
|
+
|
|
2321
|
+
it("codeFilter: gt / gte / lt / lte / neq", () => {
|
|
2322
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gt", 10))).sort()).toEqual(["2", "4"])
|
|
2323
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gte", 10))).sort()).toEqual(["1", "2", "4"])
|
|
2324
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lt", 10)))).toEqual(["3"])
|
|
2325
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lte", 10))).sort()).toEqual(["1", "3"])
|
|
2326
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "neq", 0))).sort()).toEqual(["1", "2", "4"])
|
|
2327
|
+
})
|
|
2328
|
+
|
|
2329
|
+
it("codeFilter: nested path access through dot notation", () => {
|
|
2330
|
+
expect(runCF(make<CFRow>().pipe(where("nested.kind", "k1"))).sort()).toEqual(["1", "3"])
|
|
2331
|
+
expect(
|
|
2332
|
+
runCF(
|
|
2333
|
+
make<CFRow>().pipe(
|
|
2334
|
+
where("nested.kind", "k2"),
|
|
2335
|
+
and("nested.v", "gt", 0)
|
|
2336
|
+
)
|
|
2337
|
+
)
|
|
2338
|
+
)
|
|
2339
|
+
.toEqual(["2"])
|
|
2340
|
+
})
|
|
2341
|
+
|
|
2342
|
+
it("codeFilter: array length predicates", () => {
|
|
2343
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", 0)))).toEqual(["4"])
|
|
2344
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", "gte", 2))).sort()).toEqual(["1", "3"])
|
|
2345
|
+
})
|
|
2346
|
+
|
|
2347
|
+
it("codeFilter: order + skip + limit applied after filter", () => {
|
|
2348
|
+
const q = make<CFRow>().pipe(
|
|
2349
|
+
where("tag", "neq", "z"),
|
|
2350
|
+
order("qty", "DESC"),
|
|
2351
|
+
page({ skip: 1, take: 2 })
|
|
2352
|
+
)
|
|
2353
|
+
expect(runCF(q)).toEqual(["2", "1"])
|
|
2354
|
+
})
|
|
2355
|
+
|
|
2356
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2357
|
+
// memFilter: aggregate (GROUP BY) support
|
|
2358
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2359
|
+
|
|
2360
|
+
it("memFilter: agg-count-when groups rows and counts conditionally", () => {
|
|
2361
|
+
type Row = { city: string; status: string }
|
|
2362
|
+
const rows: Row[] = [
|
|
2363
|
+
{ city: "NYC", status: "active" },
|
|
2364
|
+
{ city: "NYC", status: "inactive" },
|
|
2365
|
+
{ city: "NYC", status: "active" },
|
|
2366
|
+
{ city: "LA", status: "active" }
|
|
2367
|
+
]
|
|
2368
|
+
const result = memFilter<Row>({
|
|
2369
|
+
t: {} as Row,
|
|
2370
|
+
select: [
|
|
2371
|
+
{ key: "city", path: "city" },
|
|
2372
|
+
{
|
|
2373
|
+
key: "activeCount",
|
|
2374
|
+
aggregate: {
|
|
2375
|
+
_tag: "agg-count-when",
|
|
2376
|
+
filter: [{ t: "where", path: "status", op: "eq", value: "active" }]
|
|
2377
|
+
}
|
|
2378
|
+
},
|
|
2379
|
+
{ key: "total", aggregate: { _tag: "agg-count" } }
|
|
2380
|
+
] as any
|
|
2381
|
+
})(rows) as any[]
|
|
2382
|
+
|
|
2383
|
+
expect(result.length).toBe(2)
|
|
2384
|
+
const nyc = result.find((r: any) => r.city === "NYC")!
|
|
2385
|
+
expect(nyc.activeCount).toBe(2)
|
|
2386
|
+
expect(nyc.total).toBe(3)
|
|
2387
|
+
const la = result.find((r: any) => r.city === "LA")!
|
|
2388
|
+
expect(la.activeCount).toBe(1)
|
|
2389
|
+
expect(la.total).toBe(1)
|
|
2390
|
+
})
|
|
2391
|
+
|
|
2392
|
+
it("memFilter: agg-sum / agg-min / agg-max aggregate numerics", () => {
|
|
2393
|
+
type Row = { dept: string; salary: number }
|
|
2394
|
+
const rows: Row[] = [
|
|
2395
|
+
{ dept: "eng", salary: 100 },
|
|
2396
|
+
{ dept: "eng", salary: 200 },
|
|
2397
|
+
{ dept: "hr", salary: 50 }
|
|
2398
|
+
]
|
|
2399
|
+
const result = memFilter<Row>({
|
|
2400
|
+
t: {} as Row,
|
|
2401
|
+
select: [
|
|
2402
|
+
{ key: "dept", path: "dept" },
|
|
2403
|
+
{ key: "total", aggregate: { _tag: "agg-sum", field: "salary" } },
|
|
2404
|
+
{ key: "min", aggregate: { _tag: "agg-min", field: "salary" } },
|
|
2405
|
+
{ key: "max", aggregate: { _tag: "agg-max", field: "salary" } }
|
|
2406
|
+
] as any
|
|
2407
|
+
})(rows) as any[]
|
|
2408
|
+
|
|
2409
|
+
expect(result.length).toBe(2)
|
|
2410
|
+
const eng = result.find((r: any) => r.dept === "eng")!
|
|
2411
|
+
expect(eng.total).toBe(300)
|
|
2412
|
+
expect(eng.min).toBe(100)
|
|
2413
|
+
expect(eng.max).toBe(200)
|
|
2414
|
+
const hr = result.find((r: any) => r.dept === "hr")!
|
|
2415
|
+
expect(hr.total).toBe(50)
|
|
2416
|
+
})
|
|
2417
|
+
|
|
2418
|
+
it("memFilter: aggregate with nested path grouping", () => {
|
|
2419
|
+
type Row = { address: { city: string }; value: number }
|
|
2420
|
+
const rows: Row[] = [
|
|
2421
|
+
{ address: { city: "NYC" }, value: 10 },
|
|
2422
|
+
{ address: { city: "NYC" }, value: 20 },
|
|
2423
|
+
{ address: { city: "LA" }, value: 5 }
|
|
2424
|
+
]
|
|
2425
|
+
const result = memFilter<Row>({
|
|
2426
|
+
t: {} as Row,
|
|
2427
|
+
select: [
|
|
2428
|
+
{ key: "city", path: "address.city" },
|
|
2429
|
+
{ key: "count", aggregate: { _tag: "agg-count" } }
|
|
2430
|
+
] as any
|
|
2431
|
+
})(rows) as any[]
|
|
2432
|
+
|
|
2433
|
+
expect(result.length).toBe(2)
|
|
2434
|
+
expect(result.find((r: any) => r.city === "NYC")!.count).toBe(2)
|
|
2435
|
+
expect(result.find((r: any) => r.city === "LA")!.count).toBe(1)
|
|
2436
|
+
})
|