@effect-app/infra 4.0.0-beta.33 → 4.0.0-beta.330
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 +2555 -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 +36 -15
- package/dist/ClusterCosmos.d.ts +64 -0
- package/dist/ClusterCosmos.d.ts.map +1 -0
- package/dist/ClusterCosmos.js +576 -0
- package/dist/ClusterServiceBus.d.ts +67 -0
- package/dist/ClusterServiceBus.d.ts.map +1 -0
- package/dist/ClusterServiceBus.js +84 -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 +16 -4
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +238 -42
- package/dist/Store/Cosmos.d.ts +2 -2
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +395 -256
- package/dist/Store/Disk.d.ts +4 -4
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +93 -42
- package/dist/Store/Memory.d.ts +7 -10
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +349 -79
- 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 +261 -0
- package/dist/Store/SQL/query.d.ts +53 -0
- package/dist/Store/SQL/query.d.ts.map +1 -0
- package/dist/Store/SQL/query.js +656 -0
- package/dist/Store/SQL.d.ts +28 -0
- package/dist/Store/SQL.d.ts.map +1 -0
- package/dist/Store/SQL.js +483 -0
- package/dist/Store/codeFilter.d.ts +4 -4
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +71 -22
- 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/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 +24 -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 +6 -6
- package/dist/logger.d.ts +4 -4
- package/dist/logger.d.ts.map +1 -1
- package/dist/middlewares.d.ts.map +1 -0
- package/dist/{api/middlewares.js → middlewares.js} +1 -1
- package/dist/{adapters/mongo-client.d.ts → mongo-client.d.ts} +3 -2
- package/dist/mongo-client.d.ts.map +1 -0
- package/dist/mongo-client.js +15 -0
- package/dist/otel.d.ts +75 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +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 +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 +256 -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 +25 -21
- package/package.json +38 -352
- package/src/CUPS.ts +54 -15
- package/src/ClusterCosmos.ts +1150 -0
- package/src/ClusterServiceBus.ts +244 -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 +275 -55
- package/src/Store/Cosmos.ts +547 -366
- package/src/Store/Disk.ts +146 -73
- package/src/Store/Memory.ts +420 -115
- package/src/Store/SQL/Pg.ts +412 -0
- package/src/Store/SQL/query.ts +748 -0
- package/src/Store/SQL.ts +806 -0
- package/src/Store/codeFilter.ts +78 -28
- package/src/Store/index.ts +25 -10
- 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 +56 -0
- 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 +6 -5
- 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 +35 -24
- 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 -2
- package/src/{api/routing → routing}/utils.ts +2 -1
- package/src/routing.ts +823 -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 +612 -0
- package/test/cluster-servicebus.test.ts +180 -0
- package/test/cluster-sqlite.test.ts +207 -0
- package/test/contextProvider.test.ts +15 -12
- package/test/controller.test.ts +27 -31
- package/test/cosmos-container-throughput.test.ts +61 -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 +29 -11
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +17 -10
- 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/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/json-lower.test.ts +129 -0
- package/test/layerUtils.test.ts +2 -2
- package/test/query.test.ts +1382 -44
- package/test/rawQuery.test.ts +433 -8
- package/test/repository-ext.test.ts +456 -0
- package/test/repository-legacy-document.test.ts +394 -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 +265 -0
- package/test/rpc-e2e-invalidation.test.ts +421 -0
- package/test/rpc-multi-middleware.test.ts +85 -10
- package/test/rpc-stream-fullstack.test.ts +307 -0
- package/test/sql-store.test.ts +1931 -0
- package/test/validateSample.test.ts +71 -43
- 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 +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 +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/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/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,41 @@
|
|
|
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 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"
|
|
5
21
|
import { inspect } from "util"
|
|
6
22
|
import { expect, expectTypeOf, it } from "vitest"
|
|
7
|
-
import {
|
|
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"
|
|
23
|
+
import { DiskStoreLayer } from "../src/Store/Disk.js"
|
|
10
24
|
import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
|
|
11
25
|
import { SomeService } from "./fixtures.js"
|
|
12
26
|
|
|
27
|
+
const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
|
|
28
|
+
|
|
13
29
|
const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
|
|
14
30
|
const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
|
|
15
31
|
const someUnion = S.Union([str, num])
|
|
16
32
|
|
|
17
33
|
export class Something extends S.Class<Something>("Something")({
|
|
18
|
-
id: S.StringId.
|
|
34
|
+
id: S.StringId.withConstructorDefault,
|
|
19
35
|
displayName: S.NonEmptyString255,
|
|
20
|
-
name: S.NullOr(S.NonEmptyString255).
|
|
21
|
-
n: S.Date.
|
|
22
|
-
union: someUnion.pipe(S.
|
|
36
|
+
name: S.NullOr(S.NonEmptyString255).withConstructorDefault,
|
|
37
|
+
n: S.Date.withConstructorDefault,
|
|
38
|
+
union: someUnion.pipe(S.withConstructorDefault(Effect.succeed({ _tag: "string" as const, value: "hi" })))
|
|
23
39
|
}) {}
|
|
24
40
|
export declare namespace Something {
|
|
25
41
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
@@ -31,7 +47,7 @@ const q = make<Something.Encoded>()
|
|
|
31
47
|
where("displayName", "Verona"),
|
|
32
48
|
or(
|
|
33
49
|
where("displayName", "Riley"),
|
|
34
|
-
and("n", "gt", "2021-01-01T00:00:00Z")
|
|
50
|
+
and("n", "gt", new Date("2021-01-01T00:00:00Z"))
|
|
35
51
|
),
|
|
36
52
|
order("displayName"),
|
|
37
53
|
page({ take: 10 }),
|
|
@@ -90,8 +106,8 @@ it("works", () => {
|
|
|
90
106
|
|
|
91
107
|
const processed = memFilter(interpreted)(items.map((_) =>
|
|
92
108
|
S.encodeUnknownSync(S.Struct({
|
|
93
|
-
...
|
|
94
|
-
displayName: S.
|
|
109
|
+
...Struct.omit(Something.fields, ["displayName"]),
|
|
110
|
+
displayName: S.Literals(["Verona", "Riley"])
|
|
95
111
|
}))(_)
|
|
96
112
|
))
|
|
97
113
|
|
|
@@ -99,7 +115,7 @@ it("works", () => {
|
|
|
99
115
|
})
|
|
100
116
|
|
|
101
117
|
// @effect-diagnostics-next-line missingEffectServiceDependency:off
|
|
102
|
-
class SomethingRepo extends
|
|
118
|
+
class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
|
|
103
119
|
make: Effect.gen(function*() {
|
|
104
120
|
return yield* makeRepo("Something", Something, {})
|
|
105
121
|
})
|
|
@@ -112,7 +128,7 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo",
|
|
|
112
128
|
})
|
|
113
129
|
)
|
|
114
130
|
.pipe(
|
|
115
|
-
Layer.provide(
|
|
131
|
+
Layer.provide(TestStoreLive)
|
|
116
132
|
)
|
|
117
133
|
}
|
|
118
134
|
|
|
@@ -131,7 +147,7 @@ it("works with repo", () =>
|
|
|
131
147
|
where("displayName", "Verona"),
|
|
132
148
|
or(
|
|
133
149
|
where("displayName", "Riley"),
|
|
134
|
-
and("n", "gt", "2021-01-01T00:00:00Z")
|
|
150
|
+
and("n", "gt", new Date("2021-01-01T00:00:00Z"))
|
|
135
151
|
),
|
|
136
152
|
order("displayName"),
|
|
137
153
|
page({ take: 10 }),
|
|
@@ -158,13 +174,163 @@ it("works with repo", () =>
|
|
|
158
174
|
|
|
159
175
|
expect(q1).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["id", "displayName"])))
|
|
160
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"])
|
|
161
182
|
})
|
|
162
183
|
.pipe(
|
|
163
184
|
Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
|
|
164
185
|
setupRequestContextFromCurrent(),
|
|
186
|
+
Effect.scoped,
|
|
165
187
|
Effect.runPromise
|
|
166
188
|
))
|
|
167
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
|
+
|
|
168
334
|
it("collect", () =>
|
|
169
335
|
Effect
|
|
170
336
|
.gen(function*() {
|
|
@@ -185,8 +351,8 @@ it("collect", () =>
|
|
|
185
351
|
})),
|
|
186
352
|
S.toType(S.Option(S.String)),
|
|
187
353
|
(_) =>
|
|
188
|
-
_.displayName === "Riley" && _.n === "2020-01-01T00:00:00.000Z"
|
|
189
|
-
? Option.some(`${_.displayName}-${_.n}`)
|
|
354
|
+
_.displayName === "Riley" && _.n.toISOString() === "2020-01-01T00:00:00.000Z"
|
|
355
|
+
? Option.some(`${_.displayName}-${_.n.toISOString()}`)
|
|
190
356
|
: Option.none()
|
|
191
357
|
),
|
|
192
358
|
"collect"
|
|
@@ -204,7 +370,7 @@ it("collect", () =>
|
|
|
204
370
|
QueryEnd<{
|
|
205
371
|
readonly id: string
|
|
206
372
|
readonly displayName: string
|
|
207
|
-
readonly n:
|
|
373
|
+
readonly n: Date
|
|
208
374
|
readonly union: {
|
|
209
375
|
readonly _tag: "string"
|
|
210
376
|
readonly value: string
|
|
@@ -233,18 +399,19 @@ it("collect", () =>
|
|
|
233
399
|
.pipe(
|
|
234
400
|
Effect.provide(Layer.mergeAll(SomethingRepo.Test, SomeService.Default)),
|
|
235
401
|
setupRequestContextFromCurrent(),
|
|
402
|
+
Effect.scoped,
|
|
236
403
|
Effect.runPromise
|
|
237
404
|
))
|
|
238
405
|
|
|
239
|
-
class Person extends S.
|
|
406
|
+
class Person extends S.TaggedClass<Person, Person.Encoded>()("person", {
|
|
240
407
|
id: S.String,
|
|
241
408
|
surname: S.String
|
|
242
409
|
}) {}
|
|
243
|
-
class Animal extends S.
|
|
410
|
+
class Animal extends S.TaggedClass<Animal, Animal.Encoded>()("animal", {
|
|
244
411
|
id: S.String,
|
|
245
412
|
surname: S.String
|
|
246
413
|
}) {}
|
|
247
|
-
class Test extends S.
|
|
414
|
+
class Test extends S.TaggedClass<Test, Test.Encoded>()("test", {
|
|
248
415
|
id: S.String
|
|
249
416
|
}) {}
|
|
250
417
|
|
|
@@ -279,7 +446,7 @@ it(
|
|
|
279
446
|
expect(result).toEqual([])
|
|
280
447
|
expect(result2).toEqual([])
|
|
281
448
|
})
|
|
282
|
-
.pipe(Effect.provide(
|
|
449
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
283
450
|
)
|
|
284
451
|
|
|
285
452
|
it(
|
|
@@ -377,7 +544,6 @@ it(
|
|
|
377
544
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
378
545
|
const query5 = make<Union>().pipe(
|
|
379
546
|
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
547
|
project(S.Struct({ id: S.String, a: S.Unknown }))
|
|
382
548
|
)
|
|
383
549
|
console.log(query5)
|
|
@@ -465,7 +631,7 @@ it(
|
|
|
465
631
|
|
|
466
632
|
expect([]).toEqual([])
|
|
467
633
|
})
|
|
468
|
-
.pipe(Effect.provide(
|
|
634
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
469
635
|
)
|
|
470
636
|
|
|
471
637
|
it(
|
|
@@ -508,7 +674,7 @@ it(
|
|
|
508
674
|
|
|
509
675
|
expect([]).toEqual([])
|
|
510
676
|
})
|
|
511
|
-
.pipe(Effect.provide(
|
|
677
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
512
678
|
)
|
|
513
679
|
|
|
514
680
|
it(
|
|
@@ -519,8 +685,8 @@ it(
|
|
|
519
685
|
const schema = S.Struct({
|
|
520
686
|
id: S.String,
|
|
521
687
|
createdAt: S.Date.pipe(
|
|
522
|
-
S.withDecodingDefault(() => new Date()
|
|
523
|
-
S.withConstructorDefault(() =>
|
|
688
|
+
S.withDecodingDefault(Effect.sync(() => new Date())),
|
|
689
|
+
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
524
690
|
)
|
|
525
691
|
})
|
|
526
692
|
const repo = yield* makeRepo(
|
|
@@ -532,8 +698,8 @@ it(
|
|
|
532
698
|
const outputSchema = S.Struct({
|
|
533
699
|
id: S.Literal("123"),
|
|
534
700
|
createdAt: S.Date.pipe(
|
|
535
|
-
S.withDecodingDefault(() => new Date()
|
|
536
|
-
S.withConstructorDefault(() =>
|
|
701
|
+
S.withDecodingDefault(Effect.sync(() => new Date())),
|
|
702
|
+
S.withConstructorDefault(Effect.sync(() => new Date()))
|
|
537
703
|
)
|
|
538
704
|
})
|
|
539
705
|
|
|
@@ -541,9 +707,470 @@ it(
|
|
|
541
707
|
|
|
542
708
|
expect(result).toEqual([])
|
|
543
709
|
})
|
|
544
|
-
.pipe(Effect.provide(
|
|
710
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
545
711
|
)
|
|
546
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
|
+
|
|
547
1174
|
it(
|
|
548
1175
|
"doesn't mess when refining fields",
|
|
549
1176
|
() =>
|
|
@@ -551,7 +1178,7 @@ it(
|
|
|
551
1178
|
.gen(function*() {
|
|
552
1179
|
const schema = S.Struct({
|
|
553
1180
|
id: S.String,
|
|
554
|
-
literals: S.
|
|
1181
|
+
literals: S.Literals(["a", "b", "c"])
|
|
555
1182
|
})
|
|
556
1183
|
|
|
557
1184
|
type Schema = typeof schema.Type
|
|
@@ -571,7 +1198,7 @@ it(
|
|
|
571
1198
|
|
|
572
1199
|
expect(result).toEqual([])
|
|
573
1200
|
})
|
|
574
|
-
.pipe(Effect.provide(
|
|
1201
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
575
1202
|
)
|
|
576
1203
|
|
|
577
1204
|
it(
|
|
@@ -581,7 +1208,7 @@ it(
|
|
|
581
1208
|
.gen(function*() {
|
|
582
1209
|
const schema = S.Struct({
|
|
583
1210
|
id: S.String,
|
|
584
|
-
literals: S.Union([S.
|
|
1211
|
+
literals: S.Union([S.Literals(["a", "b", "c"]), S.Null])
|
|
585
1212
|
})
|
|
586
1213
|
|
|
587
1214
|
type Schema = typeof schema.Type
|
|
@@ -615,7 +1242,7 @@ it(
|
|
|
615
1242
|
|
|
616
1243
|
expect(result).toEqual([])
|
|
617
1244
|
})
|
|
618
|
-
.pipe(Effect.provide(
|
|
1245
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
619
1246
|
)
|
|
620
1247
|
|
|
621
1248
|
it(
|
|
@@ -659,7 +1286,7 @@ it(
|
|
|
659
1286
|
|
|
660
1287
|
expect(result).toEqual([])
|
|
661
1288
|
})
|
|
662
|
-
.pipe(Effect.provide(
|
|
1289
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise)
|
|
663
1290
|
)
|
|
664
1291
|
|
|
665
1292
|
it("remove null from one constituent of a tagged union", () =>
|
|
@@ -708,7 +1335,7 @@ it("remove null from one constituent of a tagged union", () =>
|
|
|
708
1335
|
})[]
|
|
709
1336
|
>()
|
|
710
1337
|
})
|
|
711
|
-
.pipe(Effect.provide(
|
|
1338
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
712
1339
|
|
|
713
1340
|
it("refine 3", () =>
|
|
714
1341
|
Effect
|
|
@@ -746,7 +1373,7 @@ it("refine 3", () =>
|
|
|
746
1373
|
const resQuer1 = yield* repo.query(where("id", "AA"))
|
|
747
1374
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
748
1375
|
})
|
|
749
|
-
.pipe(Effect.provide(
|
|
1376
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
750
1377
|
|
|
751
1378
|
it("my test", () =>
|
|
752
1379
|
Effect
|
|
@@ -764,7 +1391,7 @@ it("my test", () =>
|
|
|
764
1391
|
)
|
|
765
1392
|
expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
|
|
766
1393
|
})
|
|
767
|
-
.pipe(Effect.provide(
|
|
1394
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
768
1395
|
|
|
769
1396
|
it("refine inner without imposing a projection", () =>
|
|
770
1397
|
Effect
|
|
@@ -808,7 +1435,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
808
1435
|
where("union._tag", "AA"),
|
|
809
1436
|
// But if I wanna the whole Data as output ignoring the inner refinement
|
|
810
1437
|
// I wanna be able to do so
|
|
811
|
-
project(
|
|
1438
|
+
project(Data.mapFields(Struct.pick(["union"])))
|
|
812
1439
|
)
|
|
813
1440
|
|
|
814
1441
|
expectTypeOf(query2).toEqualTypeOf<
|
|
@@ -839,7 +1466,7 @@ it("refine inner without imposing a projection", () =>
|
|
|
839
1466
|
}[]
|
|
840
1467
|
>()
|
|
841
1468
|
})
|
|
842
|
-
.pipe(Effect.provide(
|
|
1469
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
843
1470
|
|
|
844
1471
|
it("does not allow string queries on arrays", () =>
|
|
845
1472
|
Effect
|
|
@@ -873,8 +1500,51 @@ it("does not allow string queries on arrays", () =>
|
|
|
873
1500
|
expectTypeOf(good2).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
874
1501
|
expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
|
|
875
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))
|
|
876
1546
|
})
|
|
877
|
-
.pipe(Effect.provide(
|
|
1547
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
878
1548
|
|
|
879
1549
|
it("test array.length", () =>
|
|
880
1550
|
Effect
|
|
@@ -915,7 +1585,7 @@ it("test array.length", () =>
|
|
|
915
1585
|
QueryWhere<Something, Something>
|
|
916
1586
|
>()
|
|
917
1587
|
})
|
|
918
|
-
.pipe(Effect.provide(
|
|
1588
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
919
1589
|
|
|
920
1590
|
it("distribution over union", () =>
|
|
921
1591
|
Effect
|
|
@@ -939,7 +1609,7 @@ it("distribution over union", () =>
|
|
|
939
1609
|
})[]
|
|
940
1610
|
>()
|
|
941
1611
|
})
|
|
942
|
-
.pipe(Effect.provide(
|
|
1612
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
943
1613
|
|
|
944
1614
|
it("refine nested union", () =>
|
|
945
1615
|
Effect
|
|
@@ -980,7 +1650,158 @@ it("refine nested union", () =>
|
|
|
980
1650
|
}[]
|
|
981
1651
|
>()
|
|
982
1652
|
})
|
|
983
|
-
.pipe(Effect.provide(
|
|
1653
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1654
|
+
|
|
1655
|
+
it("find with transformed id", () =>
|
|
1656
|
+
Effect
|
|
1657
|
+
.gen(function*() {
|
|
1658
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1659
|
+
|
|
1660
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1661
|
+
configuratorId: ConfiguratorId,
|
|
1662
|
+
label: S.NonEmptyString50
|
|
1663
|
+
}) {}
|
|
1664
|
+
|
|
1665
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1666
|
+
S.decodeTo(
|
|
1667
|
+
S.toType(PreconfigurationId),
|
|
1668
|
+
SchemaTransformation.transformEffect({
|
|
1669
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1670
|
+
const values = value.split("_")
|
|
1671
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1672
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1673
|
+
values.join("_")
|
|
1674
|
+
)
|
|
1675
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1676
|
+
}),
|
|
1677
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1678
|
+
})
|
|
1679
|
+
),
|
|
1680
|
+
S.revealCodec
|
|
1681
|
+
)
|
|
1682
|
+
|
|
1683
|
+
const Preconfiguration = S.Struct({
|
|
1684
|
+
id: PreconfigurationIdFromString,
|
|
1685
|
+
name: S.String
|
|
1686
|
+
})
|
|
1687
|
+
|
|
1688
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
|
|
1689
|
+
|
|
1690
|
+
const id = new PreconfigurationId({
|
|
1691
|
+
configuratorId: S.NonEmptyString255("myConfigurator"),
|
|
1692
|
+
label: S.NonEmptyString50("myLabel")
|
|
1693
|
+
})
|
|
1694
|
+
const item = { id, name: "test preconfig" }
|
|
1695
|
+
|
|
1696
|
+
yield* repo.saveAndPublish([item])
|
|
1697
|
+
|
|
1698
|
+
const found = yield* repo.find(id)
|
|
1699
|
+
expect(Option.isSome(found)).toBe(true)
|
|
1700
|
+
expect(Option.getOrThrow(found).name).toBe("test preconfig")
|
|
1701
|
+
expect(Option.getOrThrow(found).id).toEqual(id)
|
|
1702
|
+
|
|
1703
|
+
const notFound = yield* repo.find(
|
|
1704
|
+
new PreconfigurationId({
|
|
1705
|
+
configuratorId: S.NonEmptyString255("other"),
|
|
1706
|
+
label: S.NonEmptyString50("nope")
|
|
1707
|
+
})
|
|
1708
|
+
)
|
|
1709
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1710
|
+
})
|
|
1711
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
1712
|
+
|
|
1713
|
+
it("find with transformed id in tagged union", () =>
|
|
1714
|
+
Effect
|
|
1715
|
+
.gen(function*() {
|
|
1716
|
+
const ConfiguratorId = S.NonEmptyString255
|
|
1717
|
+
|
|
1718
|
+
class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
|
|
1719
|
+
configuratorId: ConfiguratorId,
|
|
1720
|
+
label: S.NonEmptyString50
|
|
1721
|
+
}) {}
|
|
1722
|
+
|
|
1723
|
+
const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
|
|
1724
|
+
S.decodeTo(
|
|
1725
|
+
S.toType(PreconfigurationId),
|
|
1726
|
+
SchemaTransformation.transformEffect({
|
|
1727
|
+
decode: Effect.fnUntraced(function*(value) {
|
|
1728
|
+
const values = value.split("_")
|
|
1729
|
+
const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
|
|
1730
|
+
const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(
|
|
1731
|
+
values.join("_")
|
|
1732
|
+
)
|
|
1733
|
+
return new PreconfigurationId({ configuratorId, label })
|
|
1734
|
+
}),
|
|
1735
|
+
encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
|
|
1736
|
+
})
|
|
1737
|
+
),
|
|
1738
|
+
S.revealCodec
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1741
|
+
class Draft extends S.TaggedClass<Draft>()("Draft", {
|
|
1742
|
+
id: PreconfigurationIdFromString,
|
|
1743
|
+
name: S.String
|
|
1744
|
+
}) {}
|
|
1745
|
+
|
|
1746
|
+
class Published extends S.TaggedClass<Published>()("Published", {
|
|
1747
|
+
id: PreconfigurationIdFromString,
|
|
1748
|
+
name: S.String,
|
|
1749
|
+
publishedAt: S.String
|
|
1750
|
+
}) {}
|
|
1751
|
+
|
|
1752
|
+
class Archived extends S.TaggedClass<Archived>()("Archived", {
|
|
1753
|
+
id: PreconfigurationIdFromString,
|
|
1754
|
+
name: S.String,
|
|
1755
|
+
archivedAt: S.String
|
|
1756
|
+
}) {}
|
|
1757
|
+
|
|
1758
|
+
const Preconfiguration = S.Union([Draft, Published, Archived])
|
|
1759
|
+
|
|
1760
|
+
const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
|
|
1761
|
+
|
|
1762
|
+
const id1 = new PreconfigurationId({
|
|
1763
|
+
configuratorId: S.NonEmptyString255("conf1"),
|
|
1764
|
+
label: S.NonEmptyString50("draft1")
|
|
1765
|
+
})
|
|
1766
|
+
const id2 = new PreconfigurationId({
|
|
1767
|
+
configuratorId: S.NonEmptyString255("conf2"),
|
|
1768
|
+
label: S.NonEmptyString50("pub1")
|
|
1769
|
+
})
|
|
1770
|
+
const id3 = new PreconfigurationId({
|
|
1771
|
+
configuratorId: S.NonEmptyString255("conf3"),
|
|
1772
|
+
label: S.NonEmptyString50("arch1")
|
|
1773
|
+
})
|
|
1774
|
+
|
|
1775
|
+
const draft = new Draft({ id: id1, name: "my draft" })
|
|
1776
|
+
const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
|
|
1777
|
+
const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
|
|
1778
|
+
|
|
1779
|
+
yield* repo.saveAndPublish([draft, published, archived])
|
|
1780
|
+
|
|
1781
|
+
// find each by their PreconfigurationId instance
|
|
1782
|
+
const foundDraft = yield* repo.find(id1)
|
|
1783
|
+
expect(Option.isSome(foundDraft)).toBe(true)
|
|
1784
|
+
expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
|
|
1785
|
+
expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
|
|
1786
|
+
|
|
1787
|
+
const foundPublished = yield* repo.find(id2)
|
|
1788
|
+
expect(Option.isSome(foundPublished)).toBe(true)
|
|
1789
|
+
expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
|
|
1790
|
+
|
|
1791
|
+
const foundArchived = yield* repo.find(id3)
|
|
1792
|
+
expect(Option.isSome(foundArchived)).toBe(true)
|
|
1793
|
+
expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
|
|
1794
|
+
|
|
1795
|
+
// not found
|
|
1796
|
+
const notFound = yield* repo.find(
|
|
1797
|
+
new PreconfigurationId({
|
|
1798
|
+
configuratorId: S.NonEmptyString255("nope"),
|
|
1799
|
+
label: S.NonEmptyString50("nope")
|
|
1800
|
+
})
|
|
1801
|
+
)
|
|
1802
|
+
expect(Option.isNone(notFound)).toBe(true)
|
|
1803
|
+
})
|
|
1804
|
+
.pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.scoped, Effect.runPromise))
|
|
984
1805
|
|
|
985
1806
|
it("refine union with nested union", () =>
|
|
986
1807
|
Effect
|
|
@@ -1095,4 +1916,521 @@ it("refine union with nested union", () =>
|
|
|
1095
1916
|
})[]
|
|
1096
1917
|
>()
|
|
1097
1918
|
})
|
|
1098
|
-
.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
|
+
})
|