@effect-app/infra 4.0.0-beta.31 → 4.0.0-beta.310
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 +2361 -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 +253 -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 +472 -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.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 +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} +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 +389 -0
- package/src/Store/SQL/query.ts +603 -0
- package/src/Store/SQL.ts +766 -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 +177 -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 +1130 -41
- package/test/rawQuery.test.ts +340 -22
- package/test/repository-ext.test.ts +426 -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 +9 -2
- 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, 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 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,470 @@ it(
|
|
|
541
552
|
|
|
542
553
|
expect(result).toEqual([])
|
|
543
554
|
})
|
|
544
|
-
.pipe(Effect.provide(
|
|
555
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
556
|
+
)
|
|
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)
|
|
545
583
|
)
|
|
546
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
|
+
// @ts-expect-error missingField is neither an encoded repo field nor a computed projection
|
|
666
|
+
projectComputed(S.Struct({ missingField: S.String }), computed({}))
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
// Key presence only (not Encoded value types) — name: number is still projectable.
|
|
670
|
+
make<Encoded>().pipe(
|
|
671
|
+
projectComputed(
|
|
672
|
+
S.Struct({ name: S.Number }),
|
|
673
|
+
computed({})
|
|
674
|
+
)
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
// Computed field *value* types are not yet constrained against the expression
|
|
678
|
+
// result (only key presence). Keeping this as a documentation call site.
|
|
679
|
+
make<Encoded>().pipe(
|
|
680
|
+
projectComputed(
|
|
681
|
+
S.Struct({ itemCount: S.String }),
|
|
682
|
+
computed({ itemCount: relation<Encoded>("items").count() })
|
|
683
|
+
)
|
|
684
|
+
)
|
|
685
|
+
|
|
686
|
+
make<Encoded>().pipe(
|
|
687
|
+
projectComputed(
|
|
688
|
+
S.Struct({ itemCount: S.Number }),
|
|
689
|
+
// @ts-expect-error extra computed keys must exist in the projection schema
|
|
690
|
+
computed({
|
|
691
|
+
itemCount: relation<Encoded>("items").count(),
|
|
692
|
+
extraCount: relation<Encoded>("items").count()
|
|
693
|
+
})
|
|
694
|
+
)
|
|
695
|
+
)
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
it("projectComputed supports union projection schemas", () => {
|
|
699
|
+
const baseSchema = S.Union([
|
|
700
|
+
S.Struct({
|
|
701
|
+
_tag: S.Literal("open"),
|
|
702
|
+
id: S.String,
|
|
703
|
+
items: S.Array(S.Struct({ articleId: S.String, weight: S.Number }))
|
|
704
|
+
}),
|
|
705
|
+
S.Struct({
|
|
706
|
+
_tag: S.Literal("closed"),
|
|
707
|
+
id: S.String,
|
|
708
|
+
closedAt: S.String
|
|
709
|
+
})
|
|
710
|
+
])
|
|
711
|
+
type Encoded = S.Codec.Encoded<typeof baseSchema>
|
|
712
|
+
const query = make<Encoded>().pipe(
|
|
713
|
+
projectComputed(
|
|
714
|
+
S.Union([
|
|
715
|
+
S.Struct({
|
|
716
|
+
_tag: S.Literal("open"),
|
|
717
|
+
id: S.String,
|
|
718
|
+
articleCount: S.Number,
|
|
719
|
+
weight: S.Number,
|
|
720
|
+
articleIds: S.Array(S.String)
|
|
721
|
+
}),
|
|
722
|
+
S.Struct({
|
|
723
|
+
_tag: S.Literal("closed"),
|
|
724
|
+
id: S.String,
|
|
725
|
+
closedAt: S.String
|
|
726
|
+
})
|
|
727
|
+
]),
|
|
728
|
+
computed({
|
|
729
|
+
articleCount: relation<Encoded>("items").count(),
|
|
730
|
+
weight: relation<Encoded>("items").sum("weight"),
|
|
731
|
+
articleIds: relation<Encoded>("items").collect("articleId")
|
|
732
|
+
})
|
|
733
|
+
)
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
const interpreted = toFilter(query, baseSchema)
|
|
737
|
+
expect(interpreted.computed && Object.keys(interpreted.computed).toSorted()).toEqual([
|
|
738
|
+
"articleCount",
|
|
739
|
+
"articleIds",
|
|
740
|
+
"weight"
|
|
741
|
+
])
|
|
742
|
+
})
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* MACS-SCANNER-API-KS class of bug: domain cancel omits pack/print ownership
|
|
746
|
+
* (`activeRequest`); a hand-built overview projection that still requires it on
|
|
747
|
+
* every branch must fail at the `projectComputed` call site, not in prod decode.
|
|
748
|
+
*/
|
|
749
|
+
it("projectComputed rejects state-owned fields required on the wrong tagged branch", () => {
|
|
750
|
+
const domain = S.Union([
|
|
751
|
+
S.Struct({
|
|
752
|
+
_tag: S.Literal("packing"),
|
|
753
|
+
id: S.String,
|
|
754
|
+
activeRequest: S.NullOr(S.String),
|
|
755
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
756
|
+
}),
|
|
757
|
+
S.Struct({
|
|
758
|
+
_tag: S.Literal("cancelled"),
|
|
759
|
+
id: S.String,
|
|
760
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
761
|
+
})
|
|
762
|
+
])
|
|
763
|
+
type DomainEnc = S.Codec.Encoded<typeof domain>
|
|
764
|
+
|
|
765
|
+
// Good: activeRequest only on packing; cancel omits it; articleCount is computed.
|
|
766
|
+
make<DomainEnc>().pipe(
|
|
767
|
+
projectComputed(
|
|
768
|
+
S.Union([
|
|
769
|
+
S.Struct({
|
|
770
|
+
_tag: S.Literal("packing"),
|
|
771
|
+
id: S.String,
|
|
772
|
+
activeRequest: S.NullOr(S.String),
|
|
773
|
+
articleCount: S.Number
|
|
774
|
+
}),
|
|
775
|
+
S.Struct({
|
|
776
|
+
_tag: S.Literal("cancelled"),
|
|
777
|
+
id: S.String,
|
|
778
|
+
articleCount: S.Number
|
|
779
|
+
})
|
|
780
|
+
]),
|
|
781
|
+
computed({
|
|
782
|
+
articleCount: relation<DomainEnc>("items").count()
|
|
783
|
+
})
|
|
784
|
+
)
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
make<DomainEnc>().pipe(
|
|
788
|
+
projectComputed(
|
|
789
|
+
S.Union([
|
|
790
|
+
S.Struct({
|
|
791
|
+
_tag: S.Literal("packing"),
|
|
792
|
+
id: S.String,
|
|
793
|
+
activeRequest: S.NullOr(S.String),
|
|
794
|
+
articleCount: S.Number
|
|
795
|
+
}),
|
|
796
|
+
S.Struct({
|
|
797
|
+
_tag: S.Literal("cancelled"),
|
|
798
|
+
id: S.String,
|
|
799
|
+
activeRequest: S.NullOr(S.String),
|
|
800
|
+
articleCount: S.Number
|
|
801
|
+
})
|
|
802
|
+
]),
|
|
803
|
+
// @ts-expect-error activeRequest is not on domain cancelled — cannot project it there
|
|
804
|
+
computed({
|
|
805
|
+
articleCount: relation<DomainEnc>("items").count()
|
|
806
|
+
})
|
|
807
|
+
)
|
|
808
|
+
)
|
|
809
|
+
})
|
|
810
|
+
|
|
811
|
+
it("ProjectableFromDomain distributes over tagged union Encoded", () => {
|
|
812
|
+
type DomainEnc =
|
|
813
|
+
| { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
|
|
814
|
+
| { readonly _tag: "cancelled"; readonly id: string }
|
|
815
|
+
|
|
816
|
+
type Good =
|
|
817
|
+
| { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null; readonly n: number }
|
|
818
|
+
| { readonly _tag: "cancelled"; readonly id: string; readonly n: number }
|
|
819
|
+
|
|
820
|
+
type Bad =
|
|
821
|
+
| { readonly _tag: "packing"; readonly id: string; readonly activeRequest: string | null }
|
|
822
|
+
| { readonly _tag: "cancelled"; readonly id: string; readonly activeRequest: string | null }
|
|
823
|
+
|
|
824
|
+
type GoodCheck = ProjectableFromDomain<Good, DomainEnc, "n">
|
|
825
|
+
type BadCheck = ProjectableFromDomain<Bad, DomainEnc>
|
|
826
|
+
|
|
827
|
+
const _good: GoodCheck = undefined as unknown
|
|
828
|
+
// @ts-expect-error cancelled branch requires activeRequest not present on domain cancelled
|
|
829
|
+
const _bad: BadCheck = undefined as unknown
|
|
830
|
+
void _good
|
|
831
|
+
void _bad
|
|
832
|
+
})
|
|
833
|
+
|
|
834
|
+
it("ProjectableFromDomain allows dual same-tag domain variants", () => {
|
|
835
|
+
// EasyLife packing/closed: two domain shapes share `_tag` with different fields.
|
|
836
|
+
type DomainEnc =
|
|
837
|
+
| { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
|
|
838
|
+
| { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
|
|
839
|
+
| { readonly _tag: "initial"; readonly id: string }
|
|
840
|
+
|
|
841
|
+
type Good =
|
|
842
|
+
| { readonly _tag: "packing"; readonly id: string; readonly packages: readonly string[] }
|
|
843
|
+
| { readonly _tag: "packing"; readonly id: string; readonly units: readonly string[] }
|
|
844
|
+
|
|
845
|
+
// Flat multi-tag + state-only field must still fail (batchId not on initial).
|
|
846
|
+
type BadFlat = {
|
|
847
|
+
readonly _tag: "packing" | "initial"
|
|
848
|
+
readonly id: string
|
|
849
|
+
readonly packages: readonly string[]
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
type GoodCheck = ProjectableFromDomain<Good, DomainEnc>
|
|
853
|
+
type BadFlatCheck = ProjectableFromDomain<BadFlat, DomainEnc>
|
|
854
|
+
|
|
855
|
+
const _good: GoodCheck = undefined as unknown
|
|
856
|
+
// @ts-expect-error packages is not on domain initial; multi-tag flat intersection rejects it
|
|
857
|
+
const _badFlat: BadFlatCheck = undefined as unknown
|
|
858
|
+
void _good
|
|
859
|
+
void _badFlat
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
it("projection schema with computed fields fails without computed map", () => {
|
|
863
|
+
const baseSchema = S.Struct({
|
|
864
|
+
id: S.String,
|
|
865
|
+
items: S.Array(S.Struct({ value: S.Number }))
|
|
866
|
+
})
|
|
867
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
868
|
+
// @ts-expect-error missing computed keys are rejected; also not projectable from domain alone
|
|
869
|
+
projectComputed(S.Struct({ pickedCount: S.NonNegativeInt }), computed({}))
|
|
870
|
+
)
|
|
871
|
+
expect(() => toFilter(query, baseSchema)).toThrowError("Missing computed projections for schema keys")
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
it("projectComputed.every emits relation-every IR", () => {
|
|
875
|
+
const baseSchema = S.Struct({
|
|
876
|
+
id: S.String,
|
|
877
|
+
items: S.Array(S.Struct({ state: S.Struct({ _tag: S.String }) }))
|
|
878
|
+
})
|
|
879
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
880
|
+
projectComputed(
|
|
881
|
+
S.Struct({ allPicked: S.Boolean }),
|
|
882
|
+
computed({
|
|
883
|
+
allPicked: relation<S.Codec.Encoded<typeof baseSchema>>("items").every(where("state._tag", "Picked"))
|
|
884
|
+
})
|
|
885
|
+
)
|
|
886
|
+
)
|
|
887
|
+
const interpreted = toFilter(query, baseSchema)
|
|
888
|
+
expect(interpreted.computed?.["allPicked"]?._tag).toBe("relation-every")
|
|
889
|
+
expect(interpreted.computed?.["allPicked"]?.path).toBe("items")
|
|
890
|
+
expect((interpreted.computed?.["allPicked"] as { filter: unknown } | undefined)?.filter).toEqual([
|
|
891
|
+
{ t: "where", path: "items.-1.state._tag", op: "eq", value: "Picked" }
|
|
892
|
+
])
|
|
893
|
+
})
|
|
894
|
+
|
|
895
|
+
it("projectComputed.distinctCount emits relation-distinct-count IR with field", () => {
|
|
896
|
+
const baseSchema = S.Struct({
|
|
897
|
+
id: S.String,
|
|
898
|
+
items: S.Array(S.Struct({ rowId: S.String, state: S.Struct({ _tag: S.String }) }))
|
|
899
|
+
})
|
|
900
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
901
|
+
projectComputed(
|
|
902
|
+
S.Struct({ positionCount: S.NonNegativeInt }),
|
|
903
|
+
computed({
|
|
904
|
+
positionCount: relation<S.Codec.Encoded<typeof baseSchema>>("items").distinctCount(
|
|
905
|
+
"rowId",
|
|
906
|
+
where("state._tag", "neq", "cancelled")
|
|
907
|
+
)
|
|
908
|
+
})
|
|
909
|
+
)
|
|
910
|
+
)
|
|
911
|
+
const interpreted = toFilter(query, baseSchema)
|
|
912
|
+
const ir = interpreted.computed?.["positionCount"]
|
|
913
|
+
expect(ir?._tag).toBe("relation-distinct-count")
|
|
914
|
+
expect((ir as { field: string } | undefined)?.field).toBe("rowId")
|
|
915
|
+
expect((ir as { filter: unknown } | undefined)?.filter).toEqual([
|
|
916
|
+
{ t: "where", path: "items.-1.state._tag", op: "neq", value: "cancelled" }
|
|
917
|
+
])
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
it("projectComputed.sum emits relation-sum IR with field", () => {
|
|
921
|
+
const baseSchema = S.Struct({
|
|
922
|
+
id: S.String,
|
|
923
|
+
items: S.Array(S.Struct({ weight: S.Number }))
|
|
924
|
+
})
|
|
925
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
926
|
+
projectComputed(
|
|
927
|
+
S.Struct({ totalWeight: S.Number }),
|
|
928
|
+
computed({ totalWeight: relation<S.Codec.Encoded<typeof baseSchema>>("items").sum("weight") })
|
|
929
|
+
)
|
|
930
|
+
)
|
|
931
|
+
const interpreted = toFilter(query, baseSchema)
|
|
932
|
+
const ir = interpreted.computed?.["totalWeight"]
|
|
933
|
+
expect(ir?._tag).toBe("relation-sum")
|
|
934
|
+
expect((ir as { field: string } | undefined)?.field).toBe("weight")
|
|
935
|
+
expect((ir as { filter: unknown } | undefined)?.filter).toEqual([])
|
|
936
|
+
})
|
|
937
|
+
|
|
938
|
+
it("projectComputed.collect / collectDistinct emit relation-collect IR", () => {
|
|
939
|
+
const baseSchema = S.Struct({
|
|
940
|
+
id: S.String,
|
|
941
|
+
items: S.Array(S.Struct({ articleId: S.String }))
|
|
942
|
+
})
|
|
943
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
944
|
+
projectComputed(
|
|
945
|
+
S.Struct({
|
|
946
|
+
all: S.Array(S.String),
|
|
947
|
+
distinct: S.Array(S.String)
|
|
948
|
+
}),
|
|
949
|
+
computed({
|
|
950
|
+
all: relation<S.Codec.Encoded<typeof baseSchema>>("items").collect("articleId"),
|
|
951
|
+
distinct: relation<S.Codec.Encoded<typeof baseSchema>>("items").collectDistinct("articleId")
|
|
952
|
+
})
|
|
953
|
+
)
|
|
954
|
+
)
|
|
955
|
+
const interpreted = toFilter(query, baseSchema)
|
|
956
|
+
const all = interpreted.computed?.["all"]
|
|
957
|
+
const distinct = interpreted.computed?.["distinct"]
|
|
958
|
+
expect(all?._tag).toBe("relation-collect")
|
|
959
|
+
expect((all as { distinct: boolean } | undefined)?.distinct).toBe(false)
|
|
960
|
+
expect(distinct?._tag).toBe("relation-collect")
|
|
961
|
+
expect((distinct as { distinct: boolean } | undefined)?.distinct).toBe(true)
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
it("projectComputed.sumExpr emits relation-sum-expr IR", () => {
|
|
965
|
+
const baseSchema = S.Struct({
|
|
966
|
+
id: S.String,
|
|
967
|
+
items: S.Array(S.Struct({
|
|
968
|
+
weight: S.Number,
|
|
969
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
970
|
+
}))
|
|
971
|
+
})
|
|
972
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
973
|
+
projectComputed(
|
|
974
|
+
S.Struct({ total: S.Number }),
|
|
975
|
+
computed({
|
|
976
|
+
total: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExpr(
|
|
977
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
978
|
+
)
|
|
979
|
+
})
|
|
980
|
+
)
|
|
981
|
+
)
|
|
982
|
+
const interpreted = toFilter(query, baseSchema)
|
|
983
|
+
const ir = interpreted.computed?.["total"]
|
|
984
|
+
expect(ir?._tag).toBe("relation-sum-expr")
|
|
985
|
+
expect((ir as { expression: unknown } | undefined)?.expression).toEqual({
|
|
986
|
+
_tag: "mul",
|
|
987
|
+
left: { _tag: "field", field: "weight" },
|
|
988
|
+
right: { _tag: "field", field: "tradeUnit.amount" }
|
|
989
|
+
})
|
|
990
|
+
})
|
|
991
|
+
|
|
992
|
+
it("projectComputed.sumExprBy emits relation-sum-expr-by IR", () => {
|
|
993
|
+
const baseSchema = S.Struct({
|
|
994
|
+
id: S.String,
|
|
995
|
+
items: S.Array(S.Struct({
|
|
996
|
+
weight: S.Number,
|
|
997
|
+
tradeUnit: S.Struct({ amount: S.Number, unit: S.String })
|
|
998
|
+
}))
|
|
999
|
+
})
|
|
1000
|
+
const query = make<S.Codec.Encoded<typeof baseSchema>>().pipe(
|
|
1001
|
+
projectComputed(
|
|
1002
|
+
S.Struct({
|
|
1003
|
+
totals: S.Array(S.Struct({ unit: S.String, total: S.Number }))
|
|
1004
|
+
}),
|
|
1005
|
+
computed({
|
|
1006
|
+
totals: relation<S.Codec.Encoded<typeof baseSchema>>("items").sumExprBy(
|
|
1007
|
+
expr.mul(expr.field("weight"), expr.field("tradeUnit.amount")),
|
|
1008
|
+
{ unit: "tradeUnit.unit" }
|
|
1009
|
+
)
|
|
1010
|
+
})
|
|
1011
|
+
)
|
|
1012
|
+
)
|
|
1013
|
+
const interpreted = toFilter(query, baseSchema)
|
|
1014
|
+
const ir = interpreted.computed?.["totals"]
|
|
1015
|
+
expect(ir?._tag).toBe("relation-sum-expr-by")
|
|
1016
|
+
expect((ir as { unit: string } | undefined)?.unit).toBe("tradeUnit.unit")
|
|
1017
|
+
})
|
|
1018
|
+
|
|
547
1019
|
it(
|
|
548
1020
|
"doesn't mess when refining fields",
|
|
549
1021
|
() =>
|
|
@@ -551,7 +1023,7 @@ it(
|
|
|
551
1023
|
.gen(function*() {
|
|
552
1024
|
const schema = S.Struct({
|
|
553
1025
|
id: S.String,
|
|
554
|
-
literals: S.
|
|
1026
|
+
literals: S.Literals(["a", "b", "c"])
|
|
555
1027
|
})
|
|
556
1028
|
|
|
557
1029
|
type Schema = typeof schema.Type
|
|
@@ -571,7 +1043,7 @@ it(
|
|
|
571
1043
|
|
|
572
1044
|
expect(result).toEqual([])
|
|
573
1045
|
})
|
|
574
|
-
.pipe(Effect.provide(
|
|
1046
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
575
1047
|
)
|
|
576
1048
|
|
|
577
1049
|
it(
|
|
@@ -581,7 +1053,7 @@ it(
|
|
|
581
1053
|
.gen(function*() {
|
|
582
1054
|
const schema = S.Struct({
|
|
583
1055
|
id: S.String,
|
|
584
|
-
literals: S.Union([S.
|
|
1056
|
+
literals: S.Union([S.Literals(["a", "b", "c"]), S.Null])
|
|
585
1057
|
})
|
|
586
1058
|
|
|
587
1059
|
type Schema = typeof schema.Type
|
|
@@ -615,7 +1087,7 @@ it(
|
|
|
615
1087
|
|
|
616
1088
|
expect(result).toEqual([])
|
|
617
1089
|
})
|
|
618
|
-
.pipe(Effect.provide(
|
|
1090
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
619
1091
|
)
|
|
620
1092
|
|
|
621
1093
|
it(
|
|
@@ -659,7 +1131,7 @@ it(
|
|
|
659
1131
|
|
|
660
1132
|
expect(result).toEqual([])
|
|
661
1133
|
})
|
|
662
|
-
.pipe(Effect.provide(
|
|
1134
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
663
1135
|
)
|
|
664
1136
|
|
|
665
1137
|
it("remove null from one constituent of a tagged union", () =>
|
|
@@ -672,7 +1144,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
672
1144
|
|
|
673
1145
|
class BB extends S.Class<BB>("BB")({
|
|
674
1146
|
id: S.Literal("BB"),
|
|
675
|
-
b: S.NullOr(S.
|
|
1147
|
+
b: S.NullOr(S.Finite)
|
|
676
1148
|
}) {}
|
|
677
1149
|
|
|
678
1150
|
type Union = AA | BB
|
|
@@ -708,7 +1180,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
708
1180
|
})[]
|
|
709
1181
|
>()
|
|
710
1182
|
})
|
|
711
|
-
.pipe(Effect.provide(
|
|
1183
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
712
1184
|
|
|
713
1185
|
it("refine 3", () =>
|
|
714
1186
|
Effect
|
|
@@ -746,7 +1218,7 @@ it("refine 3", () =>
|
|
|
746
1218
|
const resQuer1 = yield* repo.query(where("id", "AA"))
|
|
747
1219
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
748
1220
|
})
|
|
749
|
-
.pipe(Effect.provide(
|
|
1221
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
750
1222
|
|
|
751
1223
|
it("my test", () =>
|
|
752
1224
|
Effect
|
|
@@ -764,7 +1236,7 @@ it("my test", () =>
|
|
|
764
1236
|
)
|
|
765
1237
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
766
1238
|
})
|
|
767
|
-
.pipe(Effect.provide(
|
|
1239
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
768
1240
|
|
|
769
1241
|
it("refine inner without imposing a projection", () =>
|
|
770
1242
|
Effect
|
|
@@ -808,7 +1280,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
808
1280
|
where("union._tag", "AA"),
|
|
809
1281
|
// But if I wanna the whole Data as output ignoring the inner refinement
|
|
810
1282
|
// I wanna be able to do so
|
|
811
|
-
project(
|
|
1283
|
+
project(Data.mapFields(Struct.pick(["union"])))
|
|
812
1284
|
)
|
|
813
1285
|
|
|
814
1286
|
expectTypeOf(query2).toEqualTypeOf<
|
|
@@ -839,7 +1311,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
839
1311
|
}[]
|
|
840
1312
|
>()
|
|
841
1313
|
})
|
|
842
|
-
.pipe(Effect.provide(
|
|
1314
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
843
1315
|
|
|
844
1316
|
it("does not allow string queries on arrays", () =>
|
|
845
1317
|
Effect
|
|
@@ -874,7 +1346,7 @@ it("does not allow string queries on arrays", () =>
|
|
|
874
1346
|
expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
875
1347
|
expectTypeOf(good4).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
876
1348
|
})
|
|
877
|
-
.pipe(Effect.provide(
|
|
1349
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
878
1350
|
|
|
879
1351
|
it("test array.length", () =>
|
|
880
1352
|
Effect
|
|
@@ -915,7 +1387,7 @@ it("test array.length", () =>
|
|
|
915
1387
|
QueryWhere<Something, Something>
|
|
916
1388
|
>()
|
|
917
1389
|
})
|
|
918
|
-
.pipe(Effect.provide(
|
|
1390
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
919
1391
|
|
|
920
1392
|
it("distribution over union", () =>
|
|
921
1393
|
Effect
|
|
@@ -939,7 +1411,7 @@ it("distribution over union", () =>
|
|
|
939
1411
|
})[]
|
|
940
1412
|
>()
|
|
941
1413
|
})
|
|
942
|
-
.pipe(Effect.provide(
|
|
1414
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
943
1415
|
|
|
944
1416
|
it("refine nested union", () =>
|
|
945
1417
|
Effect
|
|
@@ -980,7 +1452,158 @@ it("refine nested union", () =>
|
|
|
980
1452
|
}[]
|
|
981
1453
|
>()
|
|
982
1454
|
})
|
|
983
|
-
.pipe(Effect.provide(
|
|
1455
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1456
|
+
|
|
1457
|
+
it("find with transformed id", () =>
|
|
1458
|
+
Effect
|
|
1459
|
+
.gen(function*() {
|
|
1460
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1461
|
+
|
|
1462
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1463
|
+
configuratorId: ConfiguratorId,
|
|
1464
|
+
label: S.NonEmptyString50
|
|
1465
|
+
}) {}
|
|
1466
|
+
|
|
1467
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1468
|
+
S.decodeTo(
|
|
1469
|
+
S.toType(PreconfigurationId),
|
|
1470
|
+
SchemaTransformation.transformOrFail({
|
|
1471
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1472
|
+
const values = value.split("_")
|
|
1473
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1474
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1475
|
+
values.join("_")
|
|
1476
|
+
)
|
|
1477
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1478
|
+
}),
|
|
1479
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1480
|
+
})
|
|
1481
|
+
),
|
|
1482
|
+
S.revealCodec
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
const Preconfiguration = S.Struct({
|
|
1486
|
+
id: PreconfigurationIdFromString,
|
|
1487
|
+
name: S.String
|
|
1488
|
+
})
|
|
1489
|
+
|
|
1490
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
|
|
1491
|
+
|
|
1492
|
+
const id = new PreconfigurationId({
|
|
1493
|
+
configuratorId: S.NonEmptyString255("myConfigurator"),
|
|
1494
|
+
label: S.NonEmptyString50("myLabel")
|
|
1495
|
+
})
|
|
1496
|
+
const item = { id, name: "test preconfig" }
|
|
1497
|
+
|
|
1498
|
+
yield* repo.saveAndPublish([item])
|
|
1499
|
+
|
|
1500
|
+
const found = yield* repo.find(id)
|
|
1501
|
+
expect(Option.isSome(found)).toBe(true)
|
|
1502
|
+
expect(Option.getOrThrow(found).name).toBe("test preconfig")
|
|
1503
|
+
expect(Option.getOrThrow(found).id).toEqual(id)
|
|
1504
|
+
|
|
1505
|
+
const notFound = yield* repo.find(
|
|
1506
|
+
new PreconfigurationId({
|
|
1507
|
+
configuratorId: S.NonEmptyString255("other"),
|
|
1508
|
+
label: S.NonEmptyString50("nope")
|
|
1509
|
+
})
|
|
1510
|
+
)
|
|
1511
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1512
|
+
})
|
|
1513
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1514
|
+
|
|
1515
|
+
it("find with transformed id in tagged union", () =>
|
|
1516
|
+
Effect
|
|
1517
|
+
.gen(function*() {
|
|
1518
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1519
|
+
|
|
1520
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1521
|
+
configuratorId: ConfiguratorId,
|
|
1522
|
+
label: S.NonEmptyString50
|
|
1523
|
+
}) {}
|
|
1524
|
+
|
|
1525
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1526
|
+
S.decodeTo(
|
|
1527
|
+
S.toType(PreconfigurationId),
|
|
1528
|
+
SchemaTransformation.transformOrFail({
|
|
1529
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1530
|
+
const values = value.split("_")
|
|
1531
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1532
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1533
|
+
values.join("_")
|
|
1534
|
+
)
|
|
1535
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1536
|
+
}),
|
|
1537
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1538
|
+
})
|
|
1539
|
+
),
|
|
1540
|
+
S.revealCodec
|
|
1541
|
+
)
|
|
1542
|
+
|
|
1543
|
+
class Draft extends S.TaggedClass<Draft>()("Draft", {
|
|
1544
|
+
id: PreconfigurationIdFromString,
|
|
1545
|
+
name: S.String
|
|
1546
|
+
}) {}
|
|
1547
|
+
|
|
1548
|
+
class Published extends S.TaggedClass<Published>()("Published", {
|
|
1549
|
+
id: PreconfigurationIdFromString,
|
|
1550
|
+
name: S.String,
|
|
1551
|
+
publishedAt: S.String
|
|
1552
|
+
}) {}
|
|
1553
|
+
|
|
1554
|
+
class Archived extends S.TaggedClass<Archived>()("Archived", {
|
|
1555
|
+
id: PreconfigurationIdFromString,
|
|
1556
|
+
name: S.String,
|
|
1557
|
+
archivedAt: S.String
|
|
1558
|
+
}) {}
|
|
1559
|
+
|
|
1560
|
+
const Preconfiguration = S.Union([Draft, Published, Archived])
|
|
1561
|
+
|
|
1562
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
|
|
1563
|
+
|
|
1564
|
+
const id1 = new PreconfigurationId({
|
|
1565
|
+
configuratorId: S.NonEmptyString255("conf1"),
|
|
1566
|
+
label: S.NonEmptyString50("draft1")
|
|
1567
|
+
})
|
|
1568
|
+
const id2 = new PreconfigurationId({
|
|
1569
|
+
configuratorId: S.NonEmptyString255("conf2"),
|
|
1570
|
+
label: S.NonEmptyString50("pub1")
|
|
1571
|
+
})
|
|
1572
|
+
const id3 = new PreconfigurationId({
|
|
1573
|
+
configuratorId: S.NonEmptyString255("conf3"),
|
|
1574
|
+
label: S.NonEmptyString50("arch1")
|
|
1575
|
+
})
|
|
1576
|
+
|
|
1577
|
+
const draft = new Draft({ id: id1, name: "my draft" })
|
|
1578
|
+
const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
|
|
1579
|
+
const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
|
|
1580
|
+
|
|
1581
|
+
yield* repo.saveAndPublish([draft, published, archived])
|
|
1582
|
+
|
|
1583
|
+
// find each by their PreconfigurationId instance
|
|
1584
|
+
const foundDraft = yield* repo.find(id1)
|
|
1585
|
+
expect(Option.isSome(foundDraft)).toBe(true)
|
|
1586
|
+
expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
|
|
1587
|
+
expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
|
|
1588
|
+
|
|
1589
|
+
const foundPublished = yield* repo.find(id2)
|
|
1590
|
+
expect(Option.isSome(foundPublished)).toBe(true)
|
|
1591
|
+
expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
|
|
1592
|
+
|
|
1593
|
+
const foundArchived = yield* repo.find(id3)
|
|
1594
|
+
expect(Option.isSome(foundArchived)).toBe(true)
|
|
1595
|
+
expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
|
|
1596
|
+
|
|
1597
|
+
// not found
|
|
1598
|
+
const notFound = yield* repo.find(
|
|
1599
|
+
new PreconfigurationId({
|
|
1600
|
+
configuratorId: S.NonEmptyString255("nope"),
|
|
1601
|
+
label: S.NonEmptyString50("nope")
|
|
1602
|
+
})
|
|
1603
|
+
)
|
|
1604
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1605
|
+
})
|
|
1606
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
984
1607
|
|
|
985
1608
|
it("refine union with nested union", () =>
|
|
986
1609
|
Effect
|
|
@@ -1095,4 +1718,470 @@ it("refine union with nested union", () =>
|
|
|
1095
1718
|
})[]
|
|
1096
1719
|
>()
|
|
1097
1720
|
})
|
|
1098
|
-
.pipe(Effect.provide(
|
|
1721
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1722
|
+
|
|
1723
|
+
// ---------------------------------------------------------------------------
|
|
1724
|
+
// memFilter: computed projection execution (in-memory) and code filter coverage
|
|
1725
|
+
// ---------------------------------------------------------------------------
|
|
1726
|
+
|
|
1727
|
+
const computedBaseSchema = S.Struct({
|
|
1728
|
+
id: S.String,
|
|
1729
|
+
status: S.Literals(["active", "archived"]),
|
|
1730
|
+
items: S.Array(S.Struct({
|
|
1731
|
+
id: S.String,
|
|
1732
|
+
tag: S.Literals(["a", "b", "c"]),
|
|
1733
|
+
qty: S.Finite,
|
|
1734
|
+
note: S.String
|
|
1735
|
+
}))
|
|
1736
|
+
})
|
|
1737
|
+
type ComputedBase = S.Codec.Encoded<typeof computedBaseSchema>
|
|
1738
|
+
|
|
1739
|
+
const computedRows: ComputedBase[] = [
|
|
1740
|
+
{
|
|
1741
|
+
id: "r1",
|
|
1742
|
+
status: "active",
|
|
1743
|
+
items: [
|
|
1744
|
+
{ id: "i1", tag: "a", qty: 10, note: "alpha" },
|
|
1745
|
+
{ id: "i2", tag: "a", qty: 20, note: "alpha" },
|
|
1746
|
+
{ id: "i3", tag: "b", qty: 5, note: "beta" }
|
|
1747
|
+
]
|
|
1748
|
+
},
|
|
1749
|
+
{ id: "r2", status: "active", items: [] },
|
|
1750
|
+
{
|
|
1751
|
+
id: "r3",
|
|
1752
|
+
status: "archived",
|
|
1753
|
+
items: [
|
|
1754
|
+
{ id: "i4", tag: "b", qty: 7, note: "gamma" },
|
|
1755
|
+
{ id: "i5", tag: "b", qty: 7, note: "gamma" },
|
|
1756
|
+
{ id: "i6", tag: "c", qty: 3, note: "delta" }
|
|
1757
|
+
]
|
|
1758
|
+
}
|
|
1759
|
+
]
|
|
1760
|
+
|
|
1761
|
+
it("memFilter: relation-count with filter", () => {
|
|
1762
|
+
const q = make<ComputedBase>().pipe(
|
|
1763
|
+
projectComputed(
|
|
1764
|
+
S.Struct({ id: S.String, aCount: S.NonNegativeInt }),
|
|
1765
|
+
computed({
|
|
1766
|
+
aCount: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
1767
|
+
})
|
|
1768
|
+
)
|
|
1769
|
+
)
|
|
1770
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1771
|
+
{ id: "r1", aCount: 2 },
|
|
1772
|
+
{ id: "r2", aCount: 0 },
|
|
1773
|
+
{ id: "r3", aCount: 0 }
|
|
1774
|
+
])
|
|
1775
|
+
})
|
|
1776
|
+
|
|
1777
|
+
it("memFilter: relation-any / every with filter", () => {
|
|
1778
|
+
const q = make<ComputedBase>().pipe(
|
|
1779
|
+
projectComputed(
|
|
1780
|
+
S.Struct({
|
|
1781
|
+
id: S.String,
|
|
1782
|
+
hasA: S.Boolean,
|
|
1783
|
+
allB: S.Boolean
|
|
1784
|
+
}),
|
|
1785
|
+
computed({
|
|
1786
|
+
hasA: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
1787
|
+
allB: relation<ComputedBase>("items").every(where("tag", "b"))
|
|
1788
|
+
})
|
|
1789
|
+
)
|
|
1790
|
+
)
|
|
1791
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1792
|
+
{ id: "r1", hasA: true, allB: false },
|
|
1793
|
+
// empty array: any → false, every → true (JS Array.every on [] is true)
|
|
1794
|
+
{ id: "r2", hasA: false, allB: true },
|
|
1795
|
+
{ id: "r3", hasA: false, allB: false }
|
|
1796
|
+
])
|
|
1797
|
+
})
|
|
1798
|
+
|
|
1799
|
+
it("memFilter: relation-distinct-count with filter", () => {
|
|
1800
|
+
const q = make<ComputedBase>().pipe(
|
|
1801
|
+
projectComputed(
|
|
1802
|
+
S.Struct({ id: S.String, distinctNotes: S.NonNegativeInt }),
|
|
1803
|
+
computed({
|
|
1804
|
+
distinctNotes: relation<ComputedBase>("items").distinctCount("note", where("tag", "neq", "c"))
|
|
1805
|
+
})
|
|
1806
|
+
)
|
|
1807
|
+
)
|
|
1808
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1809
|
+
{ id: "r1", distinctNotes: 2 }, // alpha, beta
|
|
1810
|
+
{ id: "r2", distinctNotes: 0 },
|
|
1811
|
+
{ id: "r3", distinctNotes: 1 } // gamma (delta filtered out)
|
|
1812
|
+
])
|
|
1813
|
+
})
|
|
1814
|
+
|
|
1815
|
+
it("memFilter: relation-sum with filter", () => {
|
|
1816
|
+
const q = make<ComputedBase>().pipe(
|
|
1817
|
+
projectComputed(
|
|
1818
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
1819
|
+
computed({
|
|
1820
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
1821
|
+
})
|
|
1822
|
+
)
|
|
1823
|
+
)
|
|
1824
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1825
|
+
{ id: "r1", totalQty: 35 },
|
|
1826
|
+
{ id: "r2", totalQty: 0 },
|
|
1827
|
+
{ id: "r3", totalQty: 14 }
|
|
1828
|
+
])
|
|
1829
|
+
})
|
|
1830
|
+
|
|
1831
|
+
it("memFilter: relation-collect / collectDistinct with filter", () => {
|
|
1832
|
+
const q = make<ComputedBase>().pipe(
|
|
1833
|
+
projectComputed(
|
|
1834
|
+
S.Struct({
|
|
1835
|
+
id: S.String,
|
|
1836
|
+
notes: S.Array(S.String),
|
|
1837
|
+
distinctNotes: S.Array(S.String)
|
|
1838
|
+
}),
|
|
1839
|
+
computed({
|
|
1840
|
+
notes: relation<ComputedBase>("items").collect("note", where("tag", "neq", "c")),
|
|
1841
|
+
distinctNotes: relation<ComputedBase>("items").collectDistinct("note", where("tag", "neq", "c"))
|
|
1842
|
+
})
|
|
1843
|
+
)
|
|
1844
|
+
)
|
|
1845
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1846
|
+
{ id: "r1", notes: ["alpha", "alpha", "beta"], distinctNotes: ["alpha", "beta"] },
|
|
1847
|
+
{ id: "r2", notes: [], distinctNotes: [] },
|
|
1848
|
+
{ id: "r3", notes: ["gamma", "gamma"], distinctNotes: ["gamma"] }
|
|
1849
|
+
])
|
|
1850
|
+
})
|
|
1851
|
+
|
|
1852
|
+
it("memFilter: computed projection with multi-statement relation filter", () => {
|
|
1853
|
+
const q = make<ComputedBase>().pipe(
|
|
1854
|
+
projectComputed(
|
|
1855
|
+
S.Struct({ id: S.String, hits: S.NonNegativeInt }),
|
|
1856
|
+
computed({
|
|
1857
|
+
hits: relation<ComputedBase>("items").count(
|
|
1858
|
+
flow(
|
|
1859
|
+
where("tag", "a"),
|
|
1860
|
+
and("qty", "gt", 10)
|
|
1861
|
+
)
|
|
1862
|
+
)
|
|
1863
|
+
})
|
|
1864
|
+
)
|
|
1865
|
+
)
|
|
1866
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1867
|
+
{ id: "r1", hits: 1 }, // only i2 (a, qty 20)
|
|
1868
|
+
{ id: "r2", hits: 0 },
|
|
1869
|
+
{ id: "r3", hits: 0 }
|
|
1870
|
+
])
|
|
1871
|
+
})
|
|
1872
|
+
|
|
1873
|
+
it("memFilter: relation-sum-expr / sum-expr-by / sum-expr-normalized", () => {
|
|
1874
|
+
const schema = S.Struct({
|
|
1875
|
+
id: S.String,
|
|
1876
|
+
items: S.Array(S.Struct({
|
|
1877
|
+
weight: S.Finite,
|
|
1878
|
+
tradeUnit: S.Struct({ amount: S.Finite, unit: S.String })
|
|
1879
|
+
}))
|
|
1880
|
+
})
|
|
1881
|
+
type Row = S.Codec.Encoded<typeof schema>
|
|
1882
|
+
const rows: Row[] = [
|
|
1883
|
+
{
|
|
1884
|
+
id: "r1",
|
|
1885
|
+
items: [
|
|
1886
|
+
{ weight: 2, tradeUnit: { amount: 5, unit: "kg" } },
|
|
1887
|
+
{ weight: 4, tradeUnit: { amount: 1000, unit: "g" } },
|
|
1888
|
+
{ weight: 3, tradeUnit: { amount: 1, unit: "kg" } }
|
|
1889
|
+
]
|
|
1890
|
+
},
|
|
1891
|
+
{ id: "r2", items: [] }
|
|
1892
|
+
]
|
|
1893
|
+
const weighted = expr.mul(expr.field("weight"), expr.field("tradeUnit.amount"))
|
|
1894
|
+
const q = make<Row>().pipe(
|
|
1895
|
+
projectComputed(
|
|
1896
|
+
S.Struct({
|
|
1897
|
+
id: S.String,
|
|
1898
|
+
totalRaw: S.Finite,
|
|
1899
|
+
totalsByUnit: S.Array(S.Struct({ unit: S.String, total: S.Finite })),
|
|
1900
|
+
totalKg: S.Finite
|
|
1901
|
+
}),
|
|
1902
|
+
computed({
|
|
1903
|
+
totalRaw: relation<Row>("items").sumExpr(weighted, where("weight", "gte", 0)),
|
|
1904
|
+
totalsByUnit: relation<Row>("items").sumExprBy(weighted, { unit: "tradeUnit.unit" }, where("weight", "gte", 0)),
|
|
1905
|
+
totalKg: relation<Row>("items").sumExprNormalized(weighted, {
|
|
1906
|
+
unit: "tradeUnit.unit",
|
|
1907
|
+
toBase: "kg",
|
|
1908
|
+
factors: { g: 0.001 }
|
|
1909
|
+
}, where("weight", "gte", 0))
|
|
1910
|
+
})
|
|
1911
|
+
)
|
|
1912
|
+
)
|
|
1913
|
+
expect(memFilter(toFilter(q, schema))(rows)).toEqual([
|
|
1914
|
+
{
|
|
1915
|
+
id: "r1",
|
|
1916
|
+
totalRaw: 4013,
|
|
1917
|
+
totalsByUnit: [{ unit: "kg", total: 13 }, { unit: "g", total: 4000 }],
|
|
1918
|
+
totalKg: 17
|
|
1919
|
+
},
|
|
1920
|
+
{ id: "r2", totalRaw: 0, totalsByUnit: [], totalKg: 0 }
|
|
1921
|
+
])
|
|
1922
|
+
})
|
|
1923
|
+
|
|
1924
|
+
it("memFilter: computed projection combined with root where filter", () => {
|
|
1925
|
+
const q = make<ComputedBase>().pipe(
|
|
1926
|
+
where("id", "neq", "r3"),
|
|
1927
|
+
projectComputed(
|
|
1928
|
+
S.Struct({ id: S.String, totalQty: S.Finite }),
|
|
1929
|
+
computed({
|
|
1930
|
+
totalQty: relation<ComputedBase>("items").sum("qty", where("tag", "neq", "c"))
|
|
1931
|
+
})
|
|
1932
|
+
)
|
|
1933
|
+
)
|
|
1934
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1935
|
+
{ id: "r1", totalQty: 35 },
|
|
1936
|
+
{ id: "r2", totalQty: 0 }
|
|
1937
|
+
])
|
|
1938
|
+
})
|
|
1939
|
+
|
|
1940
|
+
it("memFilter: computed projection with order/limit/skip applied to base rows", () => {
|
|
1941
|
+
const q = make<ComputedBase>().pipe(
|
|
1942
|
+
order("id", "DESC"),
|
|
1943
|
+
page({ skip: 1, take: 1 }),
|
|
1944
|
+
projectComputed(
|
|
1945
|
+
S.Struct({ id: S.String, total: S.NonNegativeInt }),
|
|
1946
|
+
computed({
|
|
1947
|
+
total: relation<ComputedBase>("items").count(where("qty", "gte", 0))
|
|
1948
|
+
})
|
|
1949
|
+
)
|
|
1950
|
+
)
|
|
1951
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(computedRows)).toEqual([
|
|
1952
|
+
{ id: "r2", total: 0 }
|
|
1953
|
+
])
|
|
1954
|
+
})
|
|
1955
|
+
|
|
1956
|
+
it("memFilter: computed projection - relation missing on row returns empty value", () => {
|
|
1957
|
+
const partial: ComputedBase[] = [
|
|
1958
|
+
{ id: "x1" } as ComputedBase,
|
|
1959
|
+
{ id: "x2", status: "active", items: undefined as unknown as ComputedBase["items"] }
|
|
1960
|
+
]
|
|
1961
|
+
const q = make<ComputedBase>().pipe(
|
|
1962
|
+
projectComputed(
|
|
1963
|
+
S.Struct({
|
|
1964
|
+
id: S.String,
|
|
1965
|
+
c: S.NonNegativeInt,
|
|
1966
|
+
s: S.Finite,
|
|
1967
|
+
any_: S.Boolean,
|
|
1968
|
+
every_: S.Boolean,
|
|
1969
|
+
coll: S.Array(S.String)
|
|
1970
|
+
}),
|
|
1971
|
+
computed({
|
|
1972
|
+
c: relation<ComputedBase>("items").count(where("tag", "a")),
|
|
1973
|
+
s: relation<ComputedBase>("items").sum("qty", where("tag", "a")),
|
|
1974
|
+
any_: relation<ComputedBase>("items").any(where("tag", "a")),
|
|
1975
|
+
every_: relation<ComputedBase>("items").every(where("tag", "a")),
|
|
1976
|
+
coll: relation<ComputedBase>("items").collect("note", where("tag", "a"))
|
|
1977
|
+
})
|
|
1978
|
+
)
|
|
1979
|
+
)
|
|
1980
|
+
expect(memFilter(toFilter(q, computedBaseSchema))(partial)).toEqual([
|
|
1981
|
+
{ id: "x1", c: 0, s: 0, any_: false, every_: true, coll: [] },
|
|
1982
|
+
{ id: "x2", c: 0, s: 0, any_: false, every_: true, coll: [] }
|
|
1983
|
+
])
|
|
1984
|
+
})
|
|
1985
|
+
|
|
1986
|
+
it("memFilter: rejects extra computed keys not in projection schema", () => {
|
|
1987
|
+
const q = make<ComputedBase>().pipe(
|
|
1988
|
+
projectComputed(
|
|
1989
|
+
S.Struct({ id: S.String }),
|
|
1990
|
+
// @ts-expect-error extra computed keys are rejected statically; keep runtime guard covered
|
|
1991
|
+
computed({
|
|
1992
|
+
bogus: relation<ComputedBase>("items").count(where("tag", "a"))
|
|
1993
|
+
})
|
|
1994
|
+
)
|
|
1995
|
+
)
|
|
1996
|
+
expect(() => toFilter(q, computedBaseSchema)).toThrowError(
|
|
1997
|
+
"Computed projection keys must exist in projection schema"
|
|
1998
|
+
)
|
|
1999
|
+
})
|
|
2000
|
+
|
|
2001
|
+
// ---------------------------------------------------------------------------
|
|
2002
|
+
// memFilter: code filter (where/and/or/scopes) execution coverage
|
|
2003
|
+
// ---------------------------------------------------------------------------
|
|
2004
|
+
|
|
2005
|
+
type CFRow = {
|
|
2006
|
+
readonly id: string
|
|
2007
|
+
readonly tag: "x" | "y" | "z"
|
|
2008
|
+
readonly qty: number
|
|
2009
|
+
readonly desc: string
|
|
2010
|
+
readonly tags: ReadonlyArray<string>
|
|
2011
|
+
readonly nested: { readonly kind: "k1" | "k2"; readonly v: number }
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
const cfRows: CFRow[] = [
|
|
2015
|
+
{ id: "1", tag: "x", qty: 10, desc: "Hello World", tags: ["red", "green"], nested: { kind: "k1", v: 1 } },
|
|
2016
|
+
{ id: "2", tag: "y", qty: 20, desc: "Goodbye", tags: ["blue"], nested: { kind: "k2", v: 5 } },
|
|
2017
|
+
{ id: "3", tag: "z", qty: 0, desc: "Hello again", tags: ["red", "blue", "green"], nested: { kind: "k1", v: 9 } },
|
|
2018
|
+
{ id: "4", tag: "y", qty: 30, desc: "World cup", tags: [], nested: { kind: "k2", v: 0 } }
|
|
2019
|
+
]
|
|
2020
|
+
|
|
2021
|
+
const runCF = (q: any) => (memFilter(toFilter(q))(cfRows) as unknown as readonly CFRow[]).map((_) => _.id)
|
|
2022
|
+
|
|
2023
|
+
it("codeFilter: where + and chain", () => {
|
|
2024
|
+
const q = make<CFRow>().pipe(
|
|
2025
|
+
where("tag", "y"),
|
|
2026
|
+
and("qty", "gt", 25)
|
|
2027
|
+
)
|
|
2028
|
+
expect(runCF(q)).toEqual(["4"])
|
|
2029
|
+
})
|
|
2030
|
+
|
|
2031
|
+
it("codeFilter: where + or chain", () => {
|
|
2032
|
+
const q = make<CFRow>().pipe(
|
|
2033
|
+
where("tag", "x"),
|
|
2034
|
+
or("tag", "z")
|
|
2035
|
+
)
|
|
2036
|
+
expect(runCF(q).sort()).toEqual(["1", "3"])
|
|
2037
|
+
})
|
|
2038
|
+
|
|
2039
|
+
it("codeFilter: nested scope precedence (a AND (b OR c))", () => {
|
|
2040
|
+
const q = make<CFRow>().pipe(
|
|
2041
|
+
where("tag", "y"),
|
|
2042
|
+
and(
|
|
2043
|
+
where("qty", "gt", 25),
|
|
2044
|
+
or("desc", "contains", "good")
|
|
2045
|
+
)
|
|
2046
|
+
)
|
|
2047
|
+
// tag=y AND (qty>25 OR desc contains "good") → row 2 (Goodbye) and row 4 (qty 30)
|
|
2048
|
+
expect(runCF(q).sort()).toEqual(["2", "4"])
|
|
2049
|
+
})
|
|
2050
|
+
|
|
2051
|
+
it("codeFilter: contains/startsWith/endsWith are case-insensitive", () => {
|
|
2052
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "contains", "WORLD"))).sort()).toEqual(["1", "4"])
|
|
2053
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "startsWith", "hello"))).sort()).toEqual(["1", "3"])
|
|
2054
|
+
expect(runCF(make<CFRow>().pipe(where("desc", "endsWith", "AGAIN")))).toEqual(["3"])
|
|
2055
|
+
})
|
|
2056
|
+
|
|
2057
|
+
it("codeFilter: array includes / includes-any / includes-all", () => {
|
|
2058
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes", "red"))).sort()).toEqual(["1", "3"])
|
|
2059
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-any", ["blue", "green"]))).sort()).toEqual([
|
|
2060
|
+
"1",
|
|
2061
|
+
"2",
|
|
2062
|
+
"3"
|
|
2063
|
+
])
|
|
2064
|
+
expect(runCF(make<CFRow>().pipe(where("tags", "includes-all", ["red", "blue"])))).toEqual(["3"])
|
|
2065
|
+
})
|
|
2066
|
+
|
|
2067
|
+
it("codeFilter: in / notIn", () => {
|
|
2068
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "in", ["x", "z"]))).sort()).toEqual(["1", "3"])
|
|
2069
|
+
expect(runCF(make<CFRow>().pipe(where("tag", "notIn", ["x", "z"]))).sort()).toEqual(["2", "4"])
|
|
2070
|
+
})
|
|
2071
|
+
|
|
2072
|
+
it("codeFilter: gt / gte / lt / lte / neq", () => {
|
|
2073
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gt", 10))).sort()).toEqual(["2", "4"])
|
|
2074
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "gte", 10))).sort()).toEqual(["1", "2", "4"])
|
|
2075
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lt", 10)))).toEqual(["3"])
|
|
2076
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "lte", 10))).sort()).toEqual(["1", "3"])
|
|
2077
|
+
expect(runCF(make<CFRow>().pipe(where("qty", "neq", 0))).sort()).toEqual(["1", "2", "4"])
|
|
2078
|
+
})
|
|
2079
|
+
|
|
2080
|
+
it("codeFilter: nested path access through dot notation", () => {
|
|
2081
|
+
expect(runCF(make<CFRow>().pipe(where("nested.kind", "k1"))).sort()).toEqual(["1", "3"])
|
|
2082
|
+
expect(
|
|
2083
|
+
runCF(
|
|
2084
|
+
make<CFRow>().pipe(
|
|
2085
|
+
where("nested.kind", "k2"),
|
|
2086
|
+
and("nested.v", "gt", 0)
|
|
2087
|
+
)
|
|
2088
|
+
)
|
|
2089
|
+
)
|
|
2090
|
+
.toEqual(["2"])
|
|
2091
|
+
})
|
|
2092
|
+
|
|
2093
|
+
it("codeFilter: array length predicates", () => {
|
|
2094
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", 0)))).toEqual(["4"])
|
|
2095
|
+
expect(runCF(make<CFRow>().pipe(where("tags.length", "gte", 2))).sort()).toEqual(["1", "3"])
|
|
2096
|
+
})
|
|
2097
|
+
|
|
2098
|
+
it("codeFilter: order + skip + limit applied after filter", () => {
|
|
2099
|
+
const q = make<CFRow>().pipe(
|
|
2100
|
+
where("tag", "neq", "z"),
|
|
2101
|
+
order("qty", "DESC"),
|
|
2102
|
+
page({ skip: 1, take: 2 })
|
|
2103
|
+
)
|
|
2104
|
+
expect(runCF(q)).toEqual(["2", "1"])
|
|
2105
|
+
})
|
|
2106
|
+
|
|
2107
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2108
|
+
// memFilter: aggregate (GROUP BY) support
|
|
2109
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2110
|
+
|
|
2111
|
+
it("memFilter: agg-count-when groups rows and counts conditionally", () => {
|
|
2112
|
+
type Row = { city: string; status: string }
|
|
2113
|
+
const rows: Row[] = [
|
|
2114
|
+
{ city: "NYC", status: "active" },
|
|
2115
|
+
{ city: "NYC", status: "inactive" },
|
|
2116
|
+
{ city: "NYC", status: "active" },
|
|
2117
|
+
{ city: "LA", status: "active" }
|
|
2118
|
+
]
|
|
2119
|
+
const result = memFilter<Row>({
|
|
2120
|
+
t: {} as Row,
|
|
2121
|
+
select: [
|
|
2122
|
+
{ key: "city", path: "city" },
|
|
2123
|
+
{
|
|
2124
|
+
key: "activeCount",
|
|
2125
|
+
aggregate: {
|
|
2126
|
+
_tag: "agg-count-when",
|
|
2127
|
+
filter: [{ t: "where", path: "status", op: "eq", value: "active" }]
|
|
2128
|
+
}
|
|
2129
|
+
},
|
|
2130
|
+
{ key: "total", aggregate: { _tag: "agg-count" } }
|
|
2131
|
+
] as any
|
|
2132
|
+
})(rows as any) as any[]
|
|
2133
|
+
|
|
2134
|
+
expect(result.length).toBe(2)
|
|
2135
|
+
const nyc = result.find((r: any) => r.city === "NYC")!
|
|
2136
|
+
expect(nyc.activeCount).toBe(2)
|
|
2137
|
+
expect(nyc.total).toBe(3)
|
|
2138
|
+
const la = result.find((r: any) => r.city === "LA")!
|
|
2139
|
+
expect(la.activeCount).toBe(1)
|
|
2140
|
+
expect(la.total).toBe(1)
|
|
2141
|
+
})
|
|
2142
|
+
|
|
2143
|
+
it("memFilter: agg-sum / agg-min / agg-max aggregate numerics", () => {
|
|
2144
|
+
type Row = { dept: string; salary: number }
|
|
2145
|
+
const rows: Row[] = [
|
|
2146
|
+
{ dept: "eng", salary: 100 },
|
|
2147
|
+
{ dept: "eng", salary: 200 },
|
|
2148
|
+
{ dept: "hr", salary: 50 }
|
|
2149
|
+
]
|
|
2150
|
+
const result = memFilter<Row>({
|
|
2151
|
+
t: {} as Row,
|
|
2152
|
+
select: [
|
|
2153
|
+
{ key: "dept", path: "dept" },
|
|
2154
|
+
{ key: "total", aggregate: { _tag: "agg-sum", field: "salary" } },
|
|
2155
|
+
{ key: "min", aggregate: { _tag: "agg-min", field: "salary" } },
|
|
2156
|
+
{ key: "max", aggregate: { _tag: "agg-max", field: "salary" } }
|
|
2157
|
+
] as any
|
|
2158
|
+
})(rows as any) as any[]
|
|
2159
|
+
|
|
2160
|
+
expect(result.length).toBe(2)
|
|
2161
|
+
const eng = result.find((r: any) => r.dept === "eng")!
|
|
2162
|
+
expect(eng.total).toBe(300)
|
|
2163
|
+
expect(eng.min).toBe(100)
|
|
2164
|
+
expect(eng.max).toBe(200)
|
|
2165
|
+
const hr = result.find((r: any) => r.dept === "hr")!
|
|
2166
|
+
expect(hr.total).toBe(50)
|
|
2167
|
+
})
|
|
2168
|
+
|
|
2169
|
+
it("memFilter: aggregate with nested path grouping", () => {
|
|
2170
|
+
type Row = { address: { city: string }; value: number }
|
|
2171
|
+
const rows: Row[] = [
|
|
2172
|
+
{ address: { city: "NYC" }, value: 10 },
|
|
2173
|
+
{ address: { city: "NYC" }, value: 20 },
|
|
2174
|
+
{ address: { city: "LA" }, value: 5 }
|
|
2175
|
+
]
|
|
2176
|
+
const result = memFilter<Row>({
|
|
2177
|
+
t: {} as Row,
|
|
2178
|
+
select: [
|
|
2179
|
+
{ key: "city", path: "address.city" },
|
|
2180
|
+
{ key: "count", aggregate: { _tag: "agg-count" } }
|
|
2181
|
+
] as any
|
|
2182
|
+
})(rows as any) as any[]
|
|
2183
|
+
|
|
2184
|
+
expect(result.length).toBe(2)
|
|
2185
|
+
expect(result.find((r: any) => r.city === "NYC")!.count).toBe(2)
|
|
2186
|
+
expect(result.find((r: any) => r.city === "LA")!.count).toBe(1)
|
|
2187
|
+
})
|