@effect-app/infra 4.0.0-beta.99 → 4.0.0
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 +2884 -0
- package/_check.sh +1 -1
- package/dist/CUPS.d.ts +28 -9
- package/dist/CUPS.d.ts.map +1 -1
- package/dist/CUPS.js +35 -14
- package/dist/ClusterCosmos.d.ts +67 -0
- package/dist/ClusterCosmos.d.ts.map +1 -0
- package/dist/ClusterCosmos.js +588 -0
- package/dist/ClusterServiceBus.d.ts +67 -0
- package/dist/ClusterServiceBus.d.ts.map +1 -0
- package/dist/ClusterServiceBus.js +84 -0
- package/dist/{api/ContextProvider.d.ts → ContextProvider.d.ts} +7 -4
- package/dist/ContextProvider.d.ts.map +1 -0
- package/dist/ContextProvider.js +40 -0
- package/dist/Emailer/Sendgrid.d.ts +110 -146
- package/dist/Emailer/Sendgrid.d.ts.map +1 -1
- package/dist/Emailer/Sendgrid.js +24 -19
- package/dist/Emailer/fake.d.ts +1 -1
- package/dist/Emailer/fake.d.ts.map +1 -1
- package/dist/Emailer/fake.js +4 -4
- package/dist/MainFiberSet.d.ts +11 -7
- package/dist/MainFiberSet.d.ts.map +1 -1
- package/dist/MainFiberSet.js +22 -6
- package/dist/QueueMaker/errors.d.ts +3 -1
- package/dist/QueueMaker/errors.d.ts.map +1 -1
- package/dist/QueueMaker/errors.js +4 -2
- package/dist/{adapters/SQL → SQL}/Model.d.ts +34 -45
- package/dist/SQL/Model.d.ts.map +1 -0
- package/dist/SQL/Model.js +469 -0
- package/dist/SQL.d.ts.map +1 -0
- package/dist/{adapters/SQL.js → SQL.js} +1 -1
- package/dist/Store/Cosmos/query.d.ts +16 -4
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +238 -42
- package/dist/Store/Cosmos.d.ts +2 -2
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +394 -319
- package/dist/Store/Disk.d.ts +4 -4
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +91 -39
- package/dist/Store/Memory.d.ts +7 -10
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +348 -78
- package/dist/Store/SQL/Pg.d.ts +2 -2
- package/dist/Store/SQL/Pg.d.ts.map +1 -1
- package/dist/Store/SQL/Pg.js +229 -154
- package/dist/Store/SQL/query.d.ts +19 -3
- package/dist/Store/SQL/query.d.ts.map +1 -1
- package/dist/Store/SQL/query.js +306 -12
- package/dist/Store/SQL.d.ts +22 -5
- package/dist/Store/SQL.d.ts.map +1 -1
- package/dist/Store/SQL.js +369 -98
- 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 +9 -6
- package/dist/Store/jsonDocument.d.ts +67 -0
- package/dist/Store/jsonDocument.d.ts.map +1 -0
- package/dist/Store/jsonDocument.js +107 -0
- package/dist/Store/utils.d.ts +21 -2
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +253 -10
- package/dist/WorkflowEngineCosmos.d.ts +30 -0
- package/dist/WorkflowEngineCosmos.d.ts.map +1 -0
- package/dist/WorkflowEngineCosmos.js +542 -0
- package/dist/WorkflowEngineSqlite.d.ts +24 -0
- package/dist/WorkflowEngineSqlite.d.ts.map +1 -0
- package/dist/WorkflowEngineSqlite.js +550 -0
- package/dist/arbs.d.ts +6 -3
- package/dist/arbs.d.ts.map +1 -1
- package/dist/arbs.js +20 -4
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +5 -0
- package/dist/cosmos-client.d.ts +40 -0
- package/dist/cosmos-client.d.ts.map +1 -0
- package/dist/cosmos-client.js +41 -0
- package/dist/errorReporter.d.ts +6 -4
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +23 -27
- package/dist/fileUtil.d.ts +1 -1
- package/dist/fileUtil.d.ts.map +1 -1
- package/dist/fileUtil.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/internal/RequestContextMiddleware.d.ts +5 -0
- package/dist/internal/RequestContextMiddleware.d.ts.map +1 -0
- package/dist/internal/RequestContextMiddleware.js +45 -0
- package/dist/internal/auth.d.ts +53 -0
- package/dist/internal/auth.d.ts.map +1 -0
- package/dist/internal/auth.js +180 -0
- package/dist/internal/events.d.ts +11 -0
- package/dist/internal/events.d.ts.map +1 -0
- package/dist/internal/events.js +49 -0
- package/dist/{api/internal → internal}/health.d.ts +1 -1
- package/dist/internal/health.d.ts.map +1 -0
- package/dist/internal/health.js +5 -0
- package/dist/{api/layerUtils.d.ts → layerUtils.d.ts} +10 -2
- package/dist/layerUtils.d.ts.map +1 -0
- package/dist/layerUtils.js +17 -0
- package/dist/logger/jsonLogger.d.ts +1 -1
- package/dist/logger/jsonLogger.d.ts.map +1 -1
- package/dist/logger/jsonLogger.js +5 -3
- package/dist/logger/logFmtLogger.d.ts +1 -1
- package/dist/logger/logFmtLogger.d.ts.map +1 -1
- package/dist/logger/logFmtLogger.js +3 -3
- package/dist/logger/shared.d.ts +2 -2
- package/dist/logger/shared.d.ts.map +1 -1
- package/dist/logger/shared.js +6 -6
- package/dist/logger.d.ts.map +1 -1
- package/dist/middlewares.d.ts.map +1 -0
- package/dist/{api/middlewares.js → middlewares.js} +1 -1
- package/dist/{adapters/mongo-client.d.ts → mongo-client.d.ts} +2 -1
- package/dist/mongo-client.d.ts.map +1 -0
- package/dist/mongo-client.js +15 -0
- package/dist/otel.d.ts +75 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +84 -0
- package/dist/rateLimit.d.ts +11 -3
- package/dist/rateLimit.d.ts.map +1 -1
- package/dist/rateLimit.js +7 -12
- package/dist/{adapters/redis-client.d.ts → redis-client.d.ts} +4 -1
- package/dist/redis-client.d.ts.map +1 -0
- package/dist/redis-client.js +98 -0
- package/dist/{api/reportError.d.ts → reportError.d.ts} +1 -1
- package/dist/reportError.d.ts.map +1 -0
- package/dist/reportError.js +28 -0
- package/dist/{api/routing → routing}/middleware/RouterMiddleware.d.ts +2 -1
- package/dist/routing/middleware/RouterMiddleware.d.ts.map +1 -0
- package/dist/{api/routing → routing}/middleware/RouterMiddleware.js +1 -1
- package/dist/{api/routing → routing}/middleware/middleware.d.ts +11 -6
- package/dist/routing/middleware/middleware.d.ts.map +1 -0
- package/dist/routing/middleware/middleware.js +152 -0
- package/dist/{api/routing → routing}/middleware.d.ts +0 -1
- package/dist/routing/middleware.d.ts.map +1 -0
- package/dist/{api/routing → routing}/middleware.js +1 -2
- package/dist/routing/schema/jwt.d.ts.map +1 -0
- package/dist/routing/schema/jwt.js +12 -0
- package/dist/routing/tsort.d.ts.map +1 -0
- package/dist/routing/tsort.js +51 -0
- package/dist/{api/routing → routing}/utils.d.ts +3 -3
- package/dist/routing/utils.d.ts.map +1 -0
- package/dist/routing/utils.js +45 -0
- package/dist/routing.d.ts +182 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +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 +23 -19
- package/package.json +36 -364
- package/src/CUPS.ts +53 -14
- package/src/ClusterCosmos.ts +1170 -0
- package/src/ClusterServiceBus.ts +244 -0
- package/src/{api/ContextProvider.ts → ContextProvider.ts} +10 -7
- package/src/Emailer/Sendgrid.ts +83 -60
- package/src/Emailer/fake.ts +4 -4
- package/src/MainFiberSet.ts +31 -12
- package/src/QueueMaker/errors.ts +3 -1
- package/src/{adapters/SQL → SQL}/Model.ts +48 -47
- package/src/SQL.ts +1 -0
- package/src/Store/Cosmos/query.ts +275 -55
- package/src/Store/Cosmos.ts +597 -511
- package/src/Store/Disk.ts +145 -71
- package/src/Store/Memory.ts +420 -115
- package/src/Store/SQL/Pg.ts +352 -268
- package/src/Store/SQL/query.ts +366 -20
- package/src/Store/SQL.ts +742 -293
- package/src/Store/codeFilter.ts +78 -28
- package/src/Store/index.ts +18 -13
- package/src/Store/jsonDocument.ts +143 -0
- package/src/Store/utils.ts +307 -30
- package/src/WorkflowEngineCosmos.ts +749 -0
- package/src/WorkflowEngineSqlite.ts +813 -0
- package/src/arbs.ts +25 -5
- package/src/cosmos-client.ts +72 -0
- package/src/errorReporter.ts +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 +18 -9
- package/src/{api/layerUtils.ts → layerUtils.ts} +7 -3
- package/src/logger/jsonLogger.ts +5 -3
- package/src/logger/logFmtLogger.ts +3 -3
- package/src/logger/shared.ts +6 -5
- package/src/middlewares.ts +10 -0
- package/src/{adapters/mongo-client.ts → mongo-client.ts} +3 -1
- package/src/otel.ts +177 -0
- package/src/rateLimit.ts +35 -24
- package/src/{adapters/redis-client.ts → redis-client.ts} +6 -2
- package/src/{api/reportError.ts → reportError.ts} +3 -2
- package/src/{api/routing → routing}/middleware/RouterMiddleware.ts +2 -1
- package/src/{api/routing → routing}/middleware/middleware.ts +75 -50
- package/src/routing/middleware.ts +4 -0
- package/src/{api/routing → routing}/schema/jwt.ts +2 -2
- package/src/{api/routing → routing}/utils.ts +2 -1
- package/src/routing.ts +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 +634 -0
- package/test/cluster-servicebus.test.ts +180 -0
- package/test/cluster-sqlite.test.ts +207 -0
- package/test/contextProvider.test.ts +11 -8
- package/test/controller.test.ts +23 -28
- package/test/cosmos-container-throughput.test.ts +80 -0
- package/test/cosmos-query.test.ts +368 -0
- package/test/dist/arbs-magnitude.test.d.ts.map +1 -0
- package/test/dist/arbs.test.d.ts.map +1 -0
- package/test/dist/auth.test.d.ts.map +1 -0
- package/test/dist/cluster-cosmos.test.d.ts.map +1 -0
- package/test/dist/cluster-servicebus.test.d.ts.map +1 -0
- package/test/dist/cluster-sqlite.test.d.ts.map +1 -0
- package/test/dist/contextProvider.test.d.ts.map +1 -1
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/cosmos-container-throughput.test.d.ts.map +1 -0
- package/test/dist/cosmos-query.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +17 -9
- package/test/dist/fixtures.d.ts.map +1 -1
- package/test/dist/fixtures.js +8 -3
- package/test/dist/json-lower.test.d.ts.map +1 -0
- package/test/dist/query.test.d.ts.map +1 -1
- package/test/dist/rawQuery.test.d.ts.map +1 -1
- package/test/dist/repository-ext.test.d.ts.map +1 -0
- package/test/dist/repository-legacy-document.test.d.ts.map +1 -0
- package/test/dist/requires.test.d.ts.map +1 -1
- package/test/dist/router-generator.test.d.ts.map +1 -0
- package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
- package/test/dist/rpc-context-map-streaming.test.d.ts.map +1 -0
- package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
- package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
- package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
- package/test/dist/workflow-engine-cosmos.test.d.ts.map +1 -0
- package/test/dist/workflow-engine-sqlite.test.d.ts.map +1 -0
- package/test/fixtures.ts +7 -2
- package/test/json-lower.test.ts +129 -0
- package/test/layerUtils.test.ts +2 -2
- package/test/query.test.ts +1232 -42
- package/test/rawQuery.test.ts +432 -7
- package/test/repository-ext.test.ts +456 -0
- package/test/repository-legacy-document.test.ts +394 -0
- package/test/requires.test.ts +7 -3
- package/test/router-generator.test.ts +187 -0
- package/test/routing-interruptibility.test.ts +66 -0
- package/test/rpc-context-map-streaming.test.ts +265 -0
- package/test/rpc-e2e-invalidation.test.ts +421 -0
- package/test/rpc-multi-middleware.test.ts +17 -11
- package/test/rpc-stream-fullstack.test.ts +307 -0
- package/test/sql-store.test.ts +1202 -47
- package/test/validateSample.test.ts +70 -42
- package/test/workflow-engine-cosmos.test.ts +345 -0
- package/test/workflow-engine-sqlite.test.ts +297 -0
- package/tsconfig.examples.json +1 -1
- package/tsconfig.json +9 -1
- package/tsconfig.src.json +35 -35
- package/tsconfig.test.json +2 -2
- package/dist/Emailer/service.d.ts +0 -55
- package/dist/Emailer/service.d.ts.map +0 -1
- package/dist/Emailer/service.js +0 -6
- package/dist/Emailer.d.ts +0 -2
- package/dist/Emailer.d.ts.map +0 -1
- package/dist/Emailer.js +0 -2
- package/dist/Model/Repository/ext.d.ts +0 -41
- package/dist/Model/Repository/ext.d.ts.map +0 -1
- package/dist/Model/Repository/ext.js +0 -65
- package/dist/Model/Repository/internal/internal.d.ts +0 -59
- package/dist/Model/Repository/internal/internal.d.ts.map +0 -1
- package/dist/Model/Repository/internal/internal.js +0 -320
- package/dist/Model/Repository/legacy.d.ts +0 -19
- package/dist/Model/Repository/legacy.d.ts.map +0 -1
- package/dist/Model/Repository/legacy.js +0 -2
- package/dist/Model/Repository/makeRepo.d.ts +0 -49
- package/dist/Model/Repository/makeRepo.d.ts.map +0 -1
- package/dist/Model/Repository/makeRepo.js +0 -24
- package/dist/Model/Repository/service.d.ts +0 -89
- package/dist/Model/Repository/service.d.ts.map +0 -1
- package/dist/Model/Repository/service.js +0 -2
- package/dist/Model/Repository/validation.d.ts +0 -43
- package/dist/Model/Repository/validation.d.ts.map +0 -1
- package/dist/Model/Repository/validation.js +0 -32
- package/dist/Model/Repository.d.ts +0 -6
- package/dist/Model/Repository.d.ts.map +0 -1
- package/dist/Model/Repository.js +0 -6
- package/dist/Model/dsl.d.ts +0 -32
- package/dist/Model/dsl.d.ts.map +0 -1
- package/dist/Model/dsl.js +0 -44
- package/dist/Model/filter/filterApi.d.ts +0 -30
- package/dist/Model/filter/filterApi.d.ts.map +0 -1
- package/dist/Model/filter/filterApi.js +0 -2
- package/dist/Model/filter/types/errors.d.ts +0 -29
- package/dist/Model/filter/types/errors.d.ts.map +0 -1
- package/dist/Model/filter/types/errors.js +0 -2
- package/dist/Model/filter/types/fields.d.ts +0 -15
- package/dist/Model/filter/types/fields.d.ts.map +0 -1
- package/dist/Model/filter/types/fields.js +0 -2
- package/dist/Model/filter/types/path/common.d.ts +0 -316
- package/dist/Model/filter/types/path/common.d.ts.map +0 -1
- package/dist/Model/filter/types/path/common.js +0 -2
- package/dist/Model/filter/types/path/eager.d.ts +0 -95
- package/dist/Model/filter/types/path/eager.d.ts.map +0 -1
- package/dist/Model/filter/types/path/eager.js +0 -31
- package/dist/Model/filter/types/path/index.d.ts +0 -4
- package/dist/Model/filter/types/path/index.d.ts.map +0 -1
- package/dist/Model/filter/types/path/index.js +0 -3
- package/dist/Model/filter/types/utils.d.ts +0 -79
- package/dist/Model/filter/types/utils.d.ts.map +0 -1
- package/dist/Model/filter/types/utils.js +0 -2
- package/dist/Model/filter/types/validator.d.ts +0 -30
- package/dist/Model/filter/types/validator.d.ts.map +0 -1
- package/dist/Model/filter/types/validator.js +0 -2
- package/dist/Model/filter/types.d.ts +0 -5
- package/dist/Model/filter/types.d.ts.map +0 -1
- package/dist/Model/filter/types.js +0 -7
- package/dist/Model/query/dsl.d.ts +0 -248
- package/dist/Model/query/dsl.d.ts.map +0 -1
- package/dist/Model/query/dsl.js +0 -104
- package/dist/Model/query/new-kid-interpreter.d.ts +0 -28
- package/dist/Model/query/new-kid-interpreter.d.ts.map +0 -1
- package/dist/Model/query/new-kid-interpreter.js +0 -165
- package/dist/Model/query.d.ts +0 -15
- package/dist/Model/query.d.ts.map +0 -1
- package/dist/Model/query.js +0 -3
- package/dist/Model.d.ts +0 -4
- package/dist/Model.d.ts.map +0 -1
- package/dist/Model.js +0 -4
- package/dist/Operations.d.ts +0 -55
- package/dist/Operations.d.ts.map +0 -1
- package/dist/Operations.js +0 -102
- package/dist/OperationsRepo.d.ts +0 -41
- package/dist/OperationsRepo.d.ts.map +0 -1
- package/dist/OperationsRepo.js +0 -14
- package/dist/QueueMaker/SQLQueue.d.ts +0 -14
- package/dist/QueueMaker/SQLQueue.d.ts.map +0 -1
- package/dist/QueueMaker/SQLQueue.js +0 -132
- package/dist/QueueMaker/memQueue.d.ts +0 -11
- package/dist/QueueMaker/memQueue.d.ts.map +0 -1
- package/dist/QueueMaker/memQueue.js +0 -76
- package/dist/QueueMaker/sbqueue.d.ts +0 -12
- package/dist/QueueMaker/sbqueue.d.ts.map +0 -1
- package/dist/QueueMaker/sbqueue.js +0 -91
- package/dist/QueueMaker/service.d.ts +0 -11
- package/dist/QueueMaker/service.d.ts.map +0 -1
- package/dist/QueueMaker/service.js +0 -4
- package/dist/RequestContext.d.ts +0 -68
- package/dist/RequestContext.d.ts.map +0 -1
- package/dist/RequestContext.js +0 -49
- package/dist/RequestFiberSet.d.ts +0 -36
- package/dist/RequestFiberSet.d.ts.map +0 -1
- package/dist/RequestFiberSet.js +0 -78
- package/dist/Store/ContextMapContainer.d.ts +0 -25
- package/dist/Store/ContextMapContainer.d.ts.map +0 -1
- package/dist/Store/ContextMapContainer.js +0 -77
- package/dist/Store/service.d.ts +0 -111
- package/dist/Store/service.d.ts.map +0 -1
- package/dist/Store/service.js +0 -79
- package/dist/Store.d.ts +0 -2
- package/dist/Store.d.ts.map +0 -1
- package/dist/Store.js +0 -2
- package/dist/adapters/SQL/Model.d.ts.map +0 -1
- package/dist/adapters/SQL/Model.js +0 -478
- package/dist/adapters/SQL.d.ts.map +0 -1
- package/dist/adapters/ServiceBus.d.ts +0 -58
- package/dist/adapters/ServiceBus.d.ts.map +0 -1
- package/dist/adapters/ServiceBus.js +0 -99
- package/dist/adapters/cosmos-client.d.ts +0 -14
- package/dist/adapters/cosmos-client.d.ts.map +0 -1
- package/dist/adapters/cosmos-client.js +0 -9
- package/dist/adapters/index.d.ts +0 -2
- package/dist/adapters/index.d.ts.map +0 -1
- package/dist/adapters/index.js +0 -2
- package/dist/adapters/logger.d.ts +0 -9
- package/dist/adapters/logger.d.ts.map +0 -1
- package/dist/adapters/logger.js +0 -3
- package/dist/adapters/memQueue.d.ts +0 -13
- package/dist/adapters/memQueue.d.ts.map +0 -1
- package/dist/adapters/memQueue.js +0 -20
- package/dist/adapters/mongo-client.d.ts.map +0 -1
- package/dist/adapters/mongo-client.js +0 -13
- package/dist/adapters/redis-client.d.ts.map +0 -1
- package/dist/adapters/redis-client.js +0 -94
- package/dist/api/ContextProvider.d.ts.map +0 -1
- package/dist/api/ContextProvider.js +0 -38
- package/dist/api/codec.d.ts.map +0 -1
- package/dist/api/codec.js +0 -5
- package/dist/api/internal/RequestContextMiddleware.d.ts +0 -5
- package/dist/api/internal/RequestContextMiddleware.d.ts.map +0 -1
- package/dist/api/internal/RequestContextMiddleware.js +0 -35
- package/dist/api/internal/auth.d.ts +0 -15
- package/dist/api/internal/auth.d.ts.map +0 -1
- package/dist/api/internal/auth.js +0 -47
- package/dist/api/internal/events.d.ts +0 -9
- package/dist/api/internal/events.d.ts.map +0 -1
- package/dist/api/internal/events.js +0 -42
- package/dist/api/internal/health.d.ts.map +0 -1
- package/dist/api/internal/health.js +0 -5
- package/dist/api/layerUtils.d.ts.map +0 -1
- package/dist/api/layerUtils.js +0 -16
- package/dist/api/middlewares.d.ts.map +0 -1
- package/dist/api/reportError.d.ts.map +0 -1
- package/dist/api/reportError.js +0 -27
- package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +0 -1
- package/dist/api/routing/middleware/middleware.d.ts.map +0 -1
- package/dist/api/routing/middleware/middleware.js +0 -130
- package/dist/api/routing/middleware.d.ts.map +0 -1
- package/dist/api/routing/schema/jwt.d.ts.map +0 -1
- package/dist/api/routing/schema/jwt.js +0 -12
- package/dist/api/routing/tsort.d.ts.map +0 -1
- package/dist/api/routing/tsort.js +0 -51
- package/dist/api/routing/utils.d.ts.map +0 -1
- package/dist/api/routing/utils.js +0 -44
- package/dist/api/routing.d.ts +0 -134
- package/dist/api/routing.d.ts.map +0 -1
- package/dist/api/routing.js +0 -167
- package/dist/api/setupRequest.d.ts +0 -15
- package/dist/api/setupRequest.d.ts.map +0 -1
- package/dist/api/setupRequest.js +0 -49
- package/dist/api/util.d.ts.map +0 -1
- package/dist/api/util.js +0 -14
- package/eslint.config.mjs +0 -24
- package/src/Emailer/service.ts +0 -52
- package/src/Emailer.ts +0 -1
- package/src/Model/Repository/ext.ts +0 -283
- package/src/Model/Repository/internal/internal.ts +0 -580
- package/src/Model/Repository/legacy.ts +0 -27
- package/src/Model/Repository/makeRepo.ts +0 -139
- package/src/Model/Repository/service.ts +0 -627
- package/src/Model/Repository/validation.ts +0 -31
- package/src/Model/Repository.ts +0 -5
- package/src/Model/dsl.ts +0 -128
- package/src/Model/filter/filterApi.ts +0 -60
- package/src/Model/filter/types/errors.ts +0 -47
- package/src/Model/filter/types/fields.ts +0 -50
- package/src/Model/filter/types/path/common.ts +0 -404
- package/src/Model/filter/types/path/eager.ts +0 -298
- package/src/Model/filter/types/path/index.ts +0 -4
- package/src/Model/filter/types/utils.ts +0 -128
- package/src/Model/filter/types/validator.ts +0 -46
- package/src/Model/filter/types.ts +0 -6
- package/src/Model/query/dsl.ts +0 -2110
- package/src/Model/query/new-kid-interpreter.ts +0 -210
- package/src/Model/query.ts +0 -13
- package/src/Model.ts +0 -3
- package/src/Operations.ts +0 -235
- package/src/OperationsRepo.ts +0 -16
- package/src/QueueMaker/SQLQueue.ts +0 -190
- package/src/QueueMaker/memQueue.ts +0 -130
- package/src/QueueMaker/sbqueue.ts +0 -137
- package/src/QueueMaker/service.ts +0 -17
- package/src/RequestContext.ts +0 -63
- package/src/RequestFiberSet.ts +0 -105
- package/src/Store/ContextMapContainer.ts +0 -116
- package/src/Store/index.test.ts.bak +0 -112
- package/src/Store/service.ts +0 -193
- package/src/Store.ts +0 -1
- package/src/adapters/SQL.ts +0 -1
- package/src/adapters/ServiceBus.ts +0 -209
- package/src/adapters/cosmos-client.ts +0 -16
- package/src/adapters/index.ts +0 -0
- package/src/adapters/logger.ts +0 -3
- package/src/adapters/memQueue.ts +0 -21
- package/src/api/internal/auth.ts +0 -68
- package/src/api/middlewares.ts +0 -10
- package/src/api/routing/middleware.ts +0 -6
- package/src/api/routing.ts +0 -598
- package/src/api/setupRequest.ts +0 -104
- package/test/dist/date-query.test.d.ts.map +0 -1
- package/tsconfig.json.bak +0 -12
- /package/dist/{adapters/SQL.d.ts → SQL.d.ts} +0 -0
- /package/dist/{api/codec.d.ts → codec.d.ts} +0 -0
- /package/dist/{api/middlewares.d.ts → middlewares.d.ts} +0 -0
- /package/dist/{api/routing → routing}/schema/jwt.d.ts +0 -0
- /package/dist/{api/routing → routing}/tsort.d.ts +0 -0
- /package/dist/{api/util.d.ts → util.d.ts} +0 -0
- /package/src/{api/codec.ts → codec.ts} +0 -0
- /package/src/{api/internal → internal}/health.ts +0 -0
- /package/src/{api/routing → routing}/tsort.ts +0 -0
- /package/src/{api/util.ts → util.ts} +0 -0
package/src/arbs.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
// Do not import to frontend
|
|
2
2
|
|
|
3
3
|
import { faker } from "@faker-js/faker"
|
|
4
|
-
import { type S } from "effect-app"
|
|
5
4
|
import { setFaker } from "effect-app/faker"
|
|
6
|
-
import * as
|
|
5
|
+
import type * as S from "effect-app/Schema"
|
|
6
|
+
import * as Arbitrary from "effect/Arbitrary"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as FastCheck from "fast-check"
|
|
7
9
|
import { Random } from "fast-check"
|
|
8
|
-
import
|
|
10
|
+
import { congruential32 } from "pure-rand/generator/congruential32"
|
|
9
11
|
|
|
10
|
-
const
|
|
12
|
+
const seed = 5
|
|
13
|
+
const rng = congruential32(seed)
|
|
14
|
+
const rnd = new Random(rng)
|
|
11
15
|
|
|
12
16
|
setFaker(faker)
|
|
13
17
|
|
|
@@ -15,6 +19,22 @@ export function generate<T>(arb: FastCheck.Arbitrary<T>) {
|
|
|
15
19
|
return arb.generate(rnd, undefined)
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
export function generateFromArbitrary<T>(arb: S.
|
|
22
|
+
export function generateFromArbitrary<T>(arb: S.Arbitrary<T>) {
|
|
19
23
|
return generate(arb(FastCheck))
|
|
20
24
|
}
|
|
25
|
+
|
|
26
|
+
// Native `sampleEffect({ count: 1 })` always uses attempt 0 of the master seed (edge-biased).
|
|
27
|
+
// Integer `seed + n` stayed in that bucket and collided for StringId. Mix a per-call suffix
|
|
28
|
+
// so each call is a jump-before-toss, like successive `generate(rnd)` draws.
|
|
29
|
+
let schemaSamples = 0
|
|
30
|
+
|
|
31
|
+
export function generateFromSchema<S extends S.Constraint>(schema: S) {
|
|
32
|
+
const samples = Effect.runSync(
|
|
33
|
+
Arbitrary.sampleEffect(Arbitrary.schema(schema), { count: 1, seed: `${seed}:${schemaSamples++}` })
|
|
34
|
+
)
|
|
35
|
+
const value = samples[0]
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
throw new Error("failed to sample schema")
|
|
38
|
+
}
|
|
39
|
+
return { value }
|
|
40
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ContainerRequest, CosmosClient as ComosClient_ } from "@azure/cosmos"
|
|
2
|
+
import * as Context from "effect-app/Context"
|
|
3
|
+
import * as Effect from "effect-app/Effect"
|
|
4
|
+
import * as Layer from "effect-app/Layer"
|
|
5
|
+
|
|
6
|
+
const withClient = (url: string) => Effect.sync(() => new ComosClient_(url))
|
|
7
|
+
|
|
8
|
+
export const makeCosmosClient = (url: string, dbName: string) =>
|
|
9
|
+
Effect.map(withClient(url), (x) => ({ db: x.database(dbName) }))
|
|
10
|
+
|
|
11
|
+
export class CosmosClient extends Context.Service<CosmosClient, {
|
|
12
|
+
readonly db: ReturnType<InstanceType<typeof ComosClient_>["database"]>
|
|
13
|
+
}>()("@services/CosmosClient") {}
|
|
14
|
+
|
|
15
|
+
export const db = CosmosClient.pipe(Effect.map((_) => _.db))
|
|
16
|
+
|
|
17
|
+
export const CosmosClientLayer = (cosmosUrl: string, dbName: string) =>
|
|
18
|
+
Layer.effect(CosmosClient, makeCosmosClient(cosmosUrl, dbName))
|
|
19
|
+
|
|
20
|
+
export interface CosmosContainerThroughput {
|
|
21
|
+
/**
|
|
22
|
+
* Autoscale max RU/s for containers created on demand (min 1000; Cosmos bills at
|
|
23
|
+
* least 10% of it). Only applied to containers that do not exist yet, in databases
|
|
24
|
+
* without shared throughput — those containers share the database pool instead.
|
|
25
|
+
* Unset keeps the Cosmos default: manual 400 RU/s per container, billed 24/7.
|
|
26
|
+
*/
|
|
27
|
+
readonly autoscaleMaxThroughput?: number | undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The slice of a Cosmos `Database` needed to create containers. */
|
|
31
|
+
export interface ContainerDb {
|
|
32
|
+
readonly container: (id: string) => { readonly read: () => Promise<unknown> }
|
|
33
|
+
readonly containers: { readonly createIfNotExists: (body: ContainerRequest) => Promise<unknown> }
|
|
34
|
+
readonly readOffer: () => Promise<{ readonly resource?: unknown }>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isNotFound = (e: unknown) => typeof e === "object" && e !== null && "code" in e && e.code === 404
|
|
38
|
+
|
|
39
|
+
const isServerlessOfferError = (error: unknown): boolean => {
|
|
40
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : undefined
|
|
41
|
+
const message = error instanceof Error
|
|
42
|
+
? error.message
|
|
43
|
+
: typeof error === "object" && error !== null && "message" in error
|
|
44
|
+
? String(error.message)
|
|
45
|
+
: String(error)
|
|
46
|
+
const badRequest = code === 400 || code === "BadRequest" || code === "400"
|
|
47
|
+
return badRequest && message.toLowerCase().includes("not supported for serverless")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const createContainerIfNotExists = async (
|
|
51
|
+
db: ContainerDb,
|
|
52
|
+
body: ContainerRequest & { id: string },
|
|
53
|
+
throughput?: CosmosContainerThroughput
|
|
54
|
+
) => {
|
|
55
|
+
const maxThroughput = throughput?.autoscaleMaxThroughput
|
|
56
|
+
if (maxThroughput === undefined) {
|
|
57
|
+
await db.containers.createIfNotExists(body)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
const exists = await db.container(body.id).read().then(
|
|
61
|
+
() => true,
|
|
62
|
+
(e) => isNotFound(e) ? false : Promise.reject(e)
|
|
63
|
+
)
|
|
64
|
+
if (exists) return
|
|
65
|
+
try {
|
|
66
|
+
const { resource: sharedOffer } = await db.readOffer()
|
|
67
|
+
await db.containers.createIfNotExists(sharedOffer ? body : { ...body, maxThroughput })
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (!isServerlessOfferError(error)) throw error
|
|
70
|
+
await db.containers.createIfNotExists(body)
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/errorReporter.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/node"
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from "effect-app/Effect"
|
|
3
|
+
import { getRC } from "effect-app/setupRequest"
|
|
3
4
|
import { dropUndefined, LogLevelToSentry } from "effect-app/utils"
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
5
|
+
import * as Cause from "effect/Cause"
|
|
6
|
+
import type * as LogLevel from "effect/LogLevel"
|
|
7
|
+
import { CauseException, tryToJson, tryToReport } from "./errors.ts"
|
|
8
|
+
import { InfraLogger } from "./logger.ts"
|
|
7
9
|
|
|
8
10
|
const tryCauseException = <E>(cause: Cause.Cause<E>, name: string): CauseException<E> => {
|
|
9
11
|
try {
|
|
@@ -13,47 +15,41 @@ const tryCauseException = <E>(cause: Cause.Cause<E>, name: string): CauseExcepti
|
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
export function reportError(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
const error = tryCauseException(cause, name)
|
|
31
|
-
|
|
32
|
-
yield* reportSentry(error, extras, LogLevelToSentry(level))
|
|
33
|
-
yield* InfraLogger
|
|
34
|
-
.logWithLevel(level, "Reporting error", cause)
|
|
35
|
-
.pipe(
|
|
36
|
-
Effect.annotateLogs(dropUndefined({
|
|
37
|
-
extras,
|
|
38
|
-
error: tryToReport(error),
|
|
39
|
-
cause: tryToJson(cause),
|
|
40
|
-
__error_name__: name
|
|
41
|
-
}))
|
|
42
|
-
)
|
|
43
|
-
.pipe(
|
|
44
|
-
Effect.catchCause((cause) => InfraLogger.logWarning("Failed to log error", cause)),
|
|
45
|
-
Effect.catchCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
46
|
-
)
|
|
18
|
+
export function reportError(name: string) {
|
|
19
|
+
return Effect.fnUntraced(
|
|
20
|
+
function*(
|
|
21
|
+
cause: Cause.Cause<unknown>,
|
|
22
|
+
extras?: Record<string, unknown>,
|
|
23
|
+
level: LogLevel.Severity = "Error"
|
|
24
|
+
) {
|
|
25
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
26
|
+
yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
const error = tryCauseException(cause, name)
|
|
47
30
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
yield* reportSentry(error, extras, LogLevelToSentry(level))
|
|
32
|
+
yield* InfraLogger
|
|
33
|
+
.logWithLevel(level, "Reporting error", cause)
|
|
34
|
+
.pipe(
|
|
35
|
+
Effect.annotateLogs(dropUndefined({
|
|
36
|
+
extras,
|
|
37
|
+
error: tryToReport(error),
|
|
38
|
+
cause: tryToJson(cause),
|
|
39
|
+
__error_name__: name
|
|
40
|
+
})),
|
|
41
|
+
Effect.catchCause((cause) => InfraLogger.logWarning("Failed to log error", cause)),
|
|
42
|
+
Effect.catchCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
55
43
|
)
|
|
56
|
-
|
|
44
|
+
|
|
45
|
+
return error
|
|
46
|
+
},
|
|
47
|
+
(effect) =>
|
|
48
|
+
Effect.tapCause(effect, (cause) =>
|
|
49
|
+
InfraLogger.logError("Failed to report error", cause).pipe(
|
|
50
|
+
Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
51
|
+
))
|
|
52
|
+
)
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
function reportSentry(
|
|
@@ -66,45 +62,39 @@ function reportSentry(
|
|
|
66
62
|
scope.setLevel(level)
|
|
67
63
|
if (context) scope.setContext("context", { ...context })
|
|
68
64
|
if (extras) scope.setContext("extras", extras)
|
|
69
|
-
|
|
70
|
-
scope.setContext("
|
|
65
|
+
const squashed = Cause.squash(error.originalCause)
|
|
66
|
+
scope.setContext("mainError", tryToJson(squashed))
|
|
67
|
+
scope.setContext("error", tryToReport(error))
|
|
68
|
+
scope.setContext("cause", tryToJson(error.originalCause))
|
|
71
69
|
Sentry.captureException(error, scope)
|
|
72
70
|
}))
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
export function logError<E>(
|
|
76
|
-
|
|
77
|
-
) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}))
|
|
93
|
-
)
|
|
94
|
-
})
|
|
95
|
-
.pipe(
|
|
96
|
-
Effect.tapCause(() => InfraLogger.logFatal("Failed to log error cause"))
|
|
97
|
-
)
|
|
73
|
+
export function logError<E>(name: string) {
|
|
74
|
+
return Effect.fnUntraced(
|
|
75
|
+
function*(cause: Cause.Cause<E>, extras?: Record<string, unknown>) {
|
|
76
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
77
|
+
yield* InfraLogger.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
yield* InfraLogger
|
|
81
|
+
.logWarning("Logging error", cause)
|
|
82
|
+
.pipe(Effect.annotateLogs(dropUndefined({
|
|
83
|
+
extras,
|
|
84
|
+
cause: tryToJson(cause),
|
|
85
|
+
__error_name__: name
|
|
86
|
+
})))
|
|
87
|
+
},
|
|
88
|
+
(effect) => Effect.tapCause(effect, () => InfraLogger.logFatal("Failed to log error cause"))
|
|
89
|
+
)
|
|
98
90
|
}
|
|
99
91
|
|
|
100
|
-
export
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Sentry.captureMessage(message, scope)
|
|
92
|
+
export const reportMessage = Effect.fnUntraced(function*(message: string, extras?: Record<string, unknown>) {
|
|
93
|
+
const context = yield* getRC
|
|
94
|
+
const scope = new Sentry.Scope()
|
|
95
|
+
if (context) scope.setContext("context", { ...context })
|
|
96
|
+
if (extras) scope.setContext("extras", extras)
|
|
97
|
+
Sentry.captureMessage(message, scope)
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
99
|
+
console.warn(message, extras)
|
|
100
|
+
})
|
package/src/fileUtil.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export *
|
|
1
|
+
export * from "./logger.ts"
|
|
2
|
+
export * from "./Store/index.ts"
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from "effect-app/Effect"
|
|
2
2
|
import { HttpMiddleware, HttpServerRequest, HttpServerResponse } from "effect-app/http"
|
|
3
|
+
import * as Layer from "effect-app/Layer"
|
|
4
|
+
import { Locale, LocaleRef, RequestContext, spanAttributes } from "effect-app/RequestContext"
|
|
3
5
|
import { NonEmptyString255 } from "effect-app/Schema"
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
+
import { provideOnRequestScope, requestStateLayer } from "effect-app/setupRequest"
|
|
7
|
+
import { storeId } from "effect-app/Store"
|
|
6
8
|
|
|
7
9
|
export const RequestContextMiddleware = (defaultLocale: Locale = "en") =>
|
|
8
10
|
HttpMiddleware.make((app) =>
|
|
@@ -21,18 +23,33 @@ export const RequestContextMiddleware = (defaultLocale: Locale = "en") =>
|
|
|
21
23
|
const namespace = NonEmptyString255((ns && (Array.isArray(ns) ? ns[0] : ns)) || "primary")
|
|
22
24
|
const deviceId = req.headers["x-fe-device-id"]
|
|
23
25
|
|
|
24
|
-
const requestContext =
|
|
26
|
+
const requestContext = RequestContext.make({
|
|
25
27
|
span: {
|
|
26
28
|
traceId: currentSpan.traceId,
|
|
27
29
|
spanId: currentSpan.spanId,
|
|
28
30
|
sampled: currentSpan.sampled
|
|
29
31
|
},
|
|
30
|
-
name: NonEmptyString255(req.originalUrl), // set more detailed elsewhere
|
|
32
|
+
name: NonEmptyString255(`HTTP ${req.method} ${req.originalUrl.split("?")[0]}`), // set more detailed elsewhere
|
|
31
33
|
locale,
|
|
32
34
|
namespace,
|
|
33
35
|
sourceId: deviceId ? NonEmptyString255(deviceId) : undefined
|
|
34
36
|
})
|
|
35
|
-
|
|
37
|
+
yield* Effect.annotateCurrentSpan(spanAttributes(requestContext))
|
|
38
|
+
const layer = Layer.mergeAll(
|
|
39
|
+
requestStateLayer,
|
|
40
|
+
Layer.succeed(LocaleRef, requestContext.locale),
|
|
41
|
+
Layer.succeed(storeId, requestContext.namespace)
|
|
42
|
+
)
|
|
43
|
+
// Bind layer to the request scope so ContextMap's finalizer (clear()) runs only
|
|
44
|
+
// after the response body is fully drained — not when `app` returns its
|
|
45
|
+
// HttpServerResponse value. Streaming RPC responses keep producing chunks (and
|
|
46
|
+
// using ContextMap-cached etags) after `app` returns; a sub-scope from
|
|
47
|
+
// `Effect.provide(layer)` would close too early and wipe etags mid-stream,
|
|
48
|
+
// causing spurious OptimisticConcurrencyException on later writes.
|
|
49
|
+
const res = yield* app.pipe(
|
|
50
|
+
Effect.withLogSpan(requestContext.name),
|
|
51
|
+
provideOnRequestScope(layer)
|
|
52
|
+
)
|
|
36
53
|
|
|
37
54
|
// TODO: how to set also on errors?
|
|
38
55
|
return HttpServerResponse.setHeaders(res, {
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import * as Effect from "effect-app/Effect"
|
|
2
|
+
import { HttpHeaders, HttpMiddleware, HttpServerRequest, HttpServerResponse } from "effect-app/http"
|
|
3
|
+
import * as Option from "effect-app/Option"
|
|
4
|
+
import * as Data from "effect/Data"
|
|
5
|
+
import { createRemoteJWKSet, jwtVerify } from "jose"
|
|
6
|
+
|
|
7
|
+
const getHeaders = (error: string, description: string, scopes?: ReadonlyArray<string>) => ({
|
|
8
|
+
"WWW-Authenticate": `Bearer realm="api", error="${error}", error_description="${description.replace(/"/g, "'")}"${
|
|
9
|
+
scopes ? `, scope="${scopes.join(" ")}"` : ""
|
|
10
|
+
}`
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export class UnauthorizedError extends Error {
|
|
14
|
+
readonly status: number = 401
|
|
15
|
+
readonly statusCode: number = 401
|
|
16
|
+
headers = { "WWW-Authenticate": "Bearer realm=\"api\"" }
|
|
17
|
+
|
|
18
|
+
constructor(message = "Unauthorized") {
|
|
19
|
+
super(message)
|
|
20
|
+
this.name = this.constructor.name
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class InvalidRequestError extends UnauthorizedError {
|
|
25
|
+
readonly code: string
|
|
26
|
+
override readonly status = 400
|
|
27
|
+
override readonly statusCode = 400
|
|
28
|
+
|
|
29
|
+
constructor(message = "Invalid Request", useErrorCode = true) {
|
|
30
|
+
super(message)
|
|
31
|
+
this.code = useErrorCode ? "invalid_request" : ""
|
|
32
|
+
if (useErrorCode) {
|
|
33
|
+
this.headers = getHeaders(this.code, this.message)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class InvalidTokenError extends UnauthorizedError {
|
|
39
|
+
readonly code = "invalid_token"
|
|
40
|
+
|
|
41
|
+
constructor(message = "Invalid Token") {
|
|
42
|
+
super(message)
|
|
43
|
+
this.headers = getHeaders(this.code, this.message)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class InsufficientScopeError extends UnauthorizedError {
|
|
48
|
+
readonly code = "insufficient_scope"
|
|
49
|
+
override readonly status = 403
|
|
50
|
+
override readonly statusCode = 403
|
|
51
|
+
|
|
52
|
+
constructor(scopes?: ReadonlyArray<string>, message = "Insufficient Scope") {
|
|
53
|
+
super(message)
|
|
54
|
+
this.headers = getHeaders(this.code, this.message, scopes)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface JwtVerifierOptions {
|
|
59
|
+
readonly audience?: string | Array<string> | ReadonlyArray<string>
|
|
60
|
+
readonly clockTolerance?: number
|
|
61
|
+
readonly issuer?: string
|
|
62
|
+
readonly issuerBaseURL?: string
|
|
63
|
+
readonly jwksUri?: string
|
|
64
|
+
readonly maxTokenAge?: number
|
|
65
|
+
readonly secret?: string
|
|
66
|
+
readonly strict?: boolean
|
|
67
|
+
readonly tokenSigningAlg?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface AuthOptions extends JwtVerifierOptions {
|
|
71
|
+
readonly authRequired?: boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type Config = AuthOptions
|
|
75
|
+
|
|
76
|
+
type JwtError = InsufficientScopeError | InvalidRequestError | InvalidTokenError | UnauthorizedError
|
|
77
|
+
|
|
78
|
+
type ResolvedConfigBase = {
|
|
79
|
+
readonly audience: string | Array<string> | undefined
|
|
80
|
+
readonly clockTolerance: number
|
|
81
|
+
readonly issuer: string | undefined
|
|
82
|
+
readonly maxTokenAge: number | undefined
|
|
83
|
+
readonly strict: boolean
|
|
84
|
+
readonly tokenSigningAlg: string | undefined
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type ResolvedConfig =
|
|
88
|
+
& ResolvedConfigBase
|
|
89
|
+
& (
|
|
90
|
+
| {
|
|
91
|
+
readonly key: ReturnType<typeof createRemoteJWKSet>
|
|
92
|
+
readonly keyType: "jwks"
|
|
93
|
+
}
|
|
94
|
+
| {
|
|
95
|
+
readonly key: Uint8Array
|
|
96
|
+
readonly keyType: "secret"
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null
|
|
101
|
+
|
|
102
|
+
const getErrorMessage = (error: unknown) => error instanceof Error ? error.message : String(error)
|
|
103
|
+
|
|
104
|
+
const normalizeAudience = (audience: Config["audience"]): string | Array<string> | undefined =>
|
|
105
|
+
Array.isArray(audience) ? Array.from(audience) : audience as string | undefined
|
|
106
|
+
|
|
107
|
+
const buildDiscoveryUrl = (issuerBaseURL: string) => {
|
|
108
|
+
const url = new URL(issuerBaseURL)
|
|
109
|
+
if (!url.pathname.includes("/.well-known/")) {
|
|
110
|
+
url.pathname = url.pathname.endsWith("/")
|
|
111
|
+
? `${url.pathname}.well-known/openid-configuration`
|
|
112
|
+
: `${url.pathname}/.well-known/openid-configuration`
|
|
113
|
+
}
|
|
114
|
+
url.search = ""
|
|
115
|
+
url.hash = ""
|
|
116
|
+
return url
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const fetchDiscoveryDocumentPromise = async (issuerBaseURL: string) => {
|
|
120
|
+
const response = await fetch(buildDiscoveryUrl(issuerBaseURL))
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new Error(`Failed to fetch authorization server metadata: ${response.status}`)
|
|
123
|
+
}
|
|
124
|
+
const json = await response.json()
|
|
125
|
+
if (!isRecord(json) || typeof json["issuer"] !== "string" || typeof json["jwks_uri"] !== "string") {
|
|
126
|
+
throw new Error("Invalid authorization server metadata")
|
|
127
|
+
}
|
|
128
|
+
return { issuer: json["issuer"], jwksUri: json["jwks_uri"] }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const getAuthorizationToken = (headers: HttpHeaders.Headers, authRequired: boolean) => {
|
|
132
|
+
const authorization = HttpHeaders.get(headers, "authorization")
|
|
133
|
+
if (Option.isNone(authorization)) {
|
|
134
|
+
return authRequired ? Effect.fail(new UnauthorizedError()) : Effect.succeed(Option.none<string>())
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const [scheme, token] = authorization.value.split(" ")
|
|
138
|
+
if (!scheme || !token || scheme.toLowerCase() !== "bearer") {
|
|
139
|
+
return Effect.fail(new InvalidRequestError("", false))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return Effect.succeed(Option.some(token))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const makeResolveConfig = (config: Config) => {
|
|
146
|
+
let cached: Promise<ResolvedConfig> | undefined
|
|
147
|
+
|
|
148
|
+
return Effect.tryPromise({
|
|
149
|
+
try: () => {
|
|
150
|
+
if (!cached) {
|
|
151
|
+
cached = (async (): Promise<ResolvedConfig> => {
|
|
152
|
+
const discovery = config.issuerBaseURL
|
|
153
|
+
? await fetchDiscoveryDocumentPromise(config.issuerBaseURL)
|
|
154
|
+
: undefined
|
|
155
|
+
|
|
156
|
+
const issuer = config.issuer ?? discovery?.issuer
|
|
157
|
+
const jwksUri = config.jwksUri ?? discovery?.jwksUri
|
|
158
|
+
const secret = config.secret
|
|
159
|
+
const base = {
|
|
160
|
+
audience: normalizeAudience(config.audience),
|
|
161
|
+
clockTolerance: config.clockTolerance ?? 5,
|
|
162
|
+
issuer,
|
|
163
|
+
maxTokenAge: config.maxTokenAge,
|
|
164
|
+
strict: config.strict ?? false,
|
|
165
|
+
tokenSigningAlg: config.tokenSigningAlg
|
|
166
|
+
} satisfies ResolvedConfigBase
|
|
167
|
+
|
|
168
|
+
if (!issuer && !secret) {
|
|
169
|
+
throw new InvalidRequestError("JWT config requires 'issuer', 'issuerBaseURL', or 'secret'")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!secret) {
|
|
173
|
+
if (!jwksUri) {
|
|
174
|
+
throw new InvalidRequestError("JWT config requires 'jwksUri', 'issuerBaseURL', or 'secret'")
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
...base,
|
|
179
|
+
key: createRemoteJWKSet(new URL(jwksUri)),
|
|
180
|
+
keyType: "jwks"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
...base,
|
|
186
|
+
key: new TextEncoder().encode(secret),
|
|
187
|
+
keyType: "secret"
|
|
188
|
+
}
|
|
189
|
+
})()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return cached
|
|
193
|
+
},
|
|
194
|
+
catch: (error) =>
|
|
195
|
+
error instanceof InvalidRequestError || error instanceof InvalidTokenError
|
|
196
|
+
? error
|
|
197
|
+
: new InvalidTokenError(getErrorMessage(error))
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const verifyToken =
|
|
202
|
+
(resolveConfig: Effect.Effect<ResolvedConfig, InvalidRequestError | InvalidTokenError>) => (token: string) =>
|
|
203
|
+
resolveConfig.pipe(
|
|
204
|
+
Effect.flatMap((config) => {
|
|
205
|
+
const options = {
|
|
206
|
+
clockTolerance: config.clockTolerance,
|
|
207
|
+
...(config.tokenSigningAlg ? { algorithms: [config.tokenSigningAlg] } : {}),
|
|
208
|
+
...(config.audience !== undefined ? { audience: config.audience } : {}),
|
|
209
|
+
...(config.issuer !== undefined ? { issuer: config.issuer } : {}),
|
|
210
|
+
...(config.maxTokenAge !== undefined ? { maxTokenAge: config.maxTokenAge } : {})
|
|
211
|
+
}
|
|
212
|
+
const verified = config.keyType === "jwks"
|
|
213
|
+
? Effect.tryPromise({
|
|
214
|
+
try: () => jwtVerify(token, config.key, options).then(({ protectedHeader }) => ({ protectedHeader })),
|
|
215
|
+
catch: (error) => new InvalidTokenError(getErrorMessage(error))
|
|
216
|
+
})
|
|
217
|
+
: Effect.tryPromise({
|
|
218
|
+
try: () => jwtVerify(token, config.key, options).then(({ protectedHeader }) => ({ protectedHeader })),
|
|
219
|
+
catch: (error) => new InvalidTokenError(getErrorMessage(error))
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
return verified.pipe(
|
|
223
|
+
Effect.flatMap(({ protectedHeader }) => {
|
|
224
|
+
const typ = protectedHeader.typ?.toLowerCase().replace(/^application\//, "")
|
|
225
|
+
return config.strict && typ !== "at+jwt"
|
|
226
|
+
? Effect.fail(new InvalidTokenError("Unexpected 'typ' value"))
|
|
227
|
+
: Effect.void
|
|
228
|
+
})
|
|
229
|
+
)
|
|
230
|
+
})
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
export const checkJWTI = (config: Config) => {
|
|
234
|
+
const resolveConfig = makeResolveConfig(config)
|
|
235
|
+
const verify = verifyToken(resolveConfig)
|
|
236
|
+
|
|
237
|
+
return Effect.fnUntraced(function*(headers: HttpHeaders.Headers) {
|
|
238
|
+
const token = yield* getAuthorizationToken(headers, config.authRequired !== false)
|
|
239
|
+
if (Option.isNone(token)) {
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
yield* verify(token.value)
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const checkJwt = (config: Config) => {
|
|
248
|
+
const check = checkJWTI(config)
|
|
249
|
+
return HttpMiddleware.make((app) =>
|
|
250
|
+
Effect.gen(function*() {
|
|
251
|
+
const req = yield* HttpServerRequest.HttpServerRequest
|
|
252
|
+
const response = yield* check(req.headers).pipe(
|
|
253
|
+
Effect.catch((error: JwtError) =>
|
|
254
|
+
HttpServerResponse.json({ message: error.message }, {
|
|
255
|
+
status: error.status,
|
|
256
|
+
headers: HttpHeaders.fromInput(error.headers)
|
|
257
|
+
})
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if (response) {
|
|
262
|
+
return response
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return yield* app
|
|
266
|
+
})
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export class JWTError extends Data.TaggedClass("JWTError")<{
|
|
271
|
+
error: JwtError
|
|
272
|
+
}> {}
|