@gettersethya/mira 0.1.0-alpha.2 → 0.1.0-alpha.21
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/README.md +174 -0
- package/dist/src/app/app.d.ts +45 -26
- package/dist/src/app/app.d.ts.map +1 -1
- package/dist/src/app/app.js +138 -41
- package/dist/src/app/app.js.map +1 -1
- package/dist/src/app/builder.d.ts +56 -15
- package/dist/src/app/builder.d.ts.map +1 -1
- package/dist/src/app/builder.js +56 -4
- package/dist/src/app/builder.js.map +1 -1
- package/dist/src/app/define-rule.d.ts +70 -0
- package/dist/src/app/define-rule.d.ts.map +1 -0
- package/dist/src/app/define-rule.js +42 -0
- package/dist/src/app/define-rule.js.map +1 -0
- package/dist/src/app/index.d.ts +8 -3
- package/dist/src/app/index.d.ts.map +1 -1
- package/dist/src/app/index.js +3 -0
- package/dist/src/app/index.js.map +1 -1
- package/dist/src/app/plugin.d.ts +83 -0
- package/dist/src/app/plugin.d.ts.map +1 -0
- package/dist/src/app/plugin.js +20 -0
- package/dist/src/app/plugin.js.map +1 -0
- package/dist/src/app/types.d.ts +4 -4
- package/dist/src/app/types.d.ts.map +1 -1
- package/dist/src/cache/cached-collection.d.ts +7 -5
- package/dist/src/cache/cached-collection.d.ts.map +1 -1
- package/dist/src/cache/cached-collection.js +8 -5
- package/dist/src/cache/cached-collection.js.map +1 -1
- package/dist/src/cache/collection-cache.d.ts +1 -1
- package/dist/src/cache/collection-cache.d.ts.map +1 -1
- package/dist/src/cache/collection-cache.js +1 -1
- package/dist/src/cache/collection-cache.js.map +1 -1
- package/dist/src/cache/ctx-key.d.ts +15 -0
- package/dist/src/cache/ctx-key.d.ts.map +1 -0
- package/dist/src/cache/ctx-key.js +37 -0
- package/dist/src/cache/ctx-key.js.map +1 -0
- package/dist/src/cache/index.d.ts +2 -1
- package/dist/src/cache/index.d.ts.map +1 -1
- package/dist/src/cache/index.js +2 -1
- package/dist/src/cache/index.js.map +1 -1
- package/dist/src/collection-service/collection-service.d.ts +7 -5
- package/dist/src/collection-service/collection-service.d.ts.map +1 -1
- package/dist/src/collection-service/collection-service.js +201 -51
- package/dist/src/collection-service/collection-service.js.map +1 -1
- package/dist/src/collection-service/context.d.ts +2 -0
- package/dist/src/collection-service/context.d.ts.map +1 -1
- package/dist/src/collection-service/decode.d.ts +4 -2
- package/dist/src/collection-service/decode.d.ts.map +1 -1
- package/dist/src/collection-service/decode.js +32 -16
- package/dist/src/collection-service/decode.js.map +1 -1
- package/dist/src/collection-service/errors.d.ts +1 -1
- package/dist/src/collection-service/errors.d.ts.map +1 -1
- package/dist/src/collection-service/errors.js +1 -1
- package/dist/src/collection-service/errors.js.map +1 -1
- package/dist/src/collection-service/index.d.ts +1 -1
- package/dist/src/collection-service/index.d.ts.map +1 -1
- package/dist/src/collection-service/index.js.map +1 -1
- package/dist/src/collection-service/input-schema.d.ts +5 -4
- package/dist/src/collection-service/input-schema.d.ts.map +1 -1
- package/dist/src/collection-service/input-schema.js +31 -18
- package/dist/src/collection-service/input-schema.js.map +1 -1
- package/dist/src/collection-service/where.d.ts +14 -2
- package/dist/src/collection-service/where.d.ts.map +1 -1
- package/dist/src/collection-service/where.js +35 -1
- package/dist/src/collection-service/where.js.map +1 -1
- package/dist/src/config/config.d.ts +4 -4
- package/dist/src/config/config.d.ts.map +1 -1
- package/dist/src/config/config.js +34 -5
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/types.d.ts +9 -0
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/cron/cron-service.d.ts +14 -0
- package/dist/src/cron/cron-service.d.ts.map +1 -0
- package/dist/src/cron/cron-service.js +135 -0
- package/dist/src/cron/cron-service.js.map +1 -0
- package/dist/src/cron/index.d.ts +4 -0
- package/dist/src/cron/index.d.ts.map +1 -0
- package/dist/src/cron/index.js +3 -0
- package/dist/src/cron/index.js.map +1 -0
- package/dist/src/cron/persistence.d.ts +22 -0
- package/dist/src/cron/persistence.d.ts.map +1 -0
- package/dist/src/cron/persistence.js +48 -0
- package/dist/src/cron/persistence.js.map +1 -0
- package/dist/src/cron/types.d.ts +51 -0
- package/dist/src/cron/types.d.ts.map +1 -0
- package/dist/src/cron/types.js +4 -0
- package/dist/src/cron/types.js.map +1 -0
- package/dist/src/crypto/crypto.d.ts +3 -2
- package/dist/src/crypto/crypto.d.ts.map +1 -1
- package/dist/src/crypto/crypto.js +1 -1
- package/dist/src/crypto/crypto.js.map +1 -1
- package/dist/src/crypto/node.d.ts.map +1 -1
- package/dist/src/crypto/node.js.map +1 -1
- package/dist/src/databases/sqlite.d.ts.map +1 -1
- package/dist/src/databases/sqlite.js +2 -2
- package/dist/src/databases/sqlite.js.map +1 -1
- package/dist/src/dialect/dialect-sqlite.d.ts +9 -0
- package/dist/src/dialect/dialect-sqlite.d.ts.map +1 -0
- package/dist/src/dialect/dialect-sqlite.js +109 -0
- package/dist/src/dialect/dialect-sqlite.js.map +1 -0
- package/dist/src/dialect/dialect.d.ts +26 -0
- package/dist/src/dialect/dialect.d.ts.map +1 -0
- package/dist/src/dialect/dialect.js +5 -0
- package/dist/src/dialect/dialect.js.map +1 -0
- package/dist/src/hooks/hook-collection.d.ts +5 -0
- package/dist/src/hooks/hook-collection.d.ts.map +1 -0
- package/dist/src/hooks/hook-collection.js +77 -0
- package/dist/src/hooks/hook-collection.js.map +1 -0
- package/dist/src/hooks/hook-service.d.ts +38 -0
- package/dist/src/hooks/hook-service.d.ts.map +1 -0
- package/dist/src/hooks/hook-service.js +150 -0
- package/dist/src/hooks/hook-service.js.map +1 -0
- package/dist/src/hooks/index.d.ts +4 -0
- package/dist/src/hooks/index.d.ts.map +1 -0
- package/dist/src/hooks/index.js +3 -0
- package/dist/src/hooks/index.js.map +1 -0
- package/dist/src/hooks/types.d.ts +74 -0
- package/dist/src/hooks/types.d.ts.map +1 -0
- package/dist/src/hooks/types.js +2 -0
- package/dist/src/hooks/types.js.map +1 -0
- package/dist/src/http/auth-node.d.ts.map +1 -1
- package/dist/src/http/auth-node.js +1 -1
- package/dist/src/http/auth-node.js.map +1 -1
- package/dist/src/http/auth.d.ts +3 -1
- package/dist/src/http/auth.d.ts.map +1 -1
- package/dist/src/http/auth.js +12 -2
- package/dist/src/http/auth.js.map +1 -1
- package/dist/src/http/cors.d.ts +94 -0
- package/dist/src/http/cors.d.ts.map +1 -0
- package/dist/src/http/cors.js +57 -0
- package/dist/src/http/cors.js.map +1 -0
- package/dist/src/http/errors.d.ts +1 -1
- package/dist/src/http/errors.d.ts.map +1 -1
- package/dist/src/http/errors.js +7 -7
- package/dist/src/http/errors.js.map +1 -1
- package/dist/src/http/file-serve.d.ts +4 -4
- package/dist/src/http/file-serve.d.ts.map +1 -1
- package/dist/src/http/file-serve.js +39 -34
- package/dist/src/http/file-serve.js.map +1 -1
- package/dist/src/http/file-token.d.ts +4 -3
- package/dist/src/http/file-token.d.ts.map +1 -1
- package/dist/src/http/file-token.js +19 -17
- package/dist/src/http/file-token.js.map +1 -1
- package/dist/src/http/files.d.ts +3 -3
- package/dist/src/http/files.d.ts.map +1 -1
- package/dist/src/http/files.js +5 -5
- package/dist/src/http/files.js.map +1 -1
- package/dist/src/http/index.d.ts +8 -5
- package/dist/src/http/index.d.ts.map +1 -1
- package/dist/src/http/index.js +5 -3
- package/dist/src/http/index.js.map +1 -1
- package/dist/src/http/ip-middleware.d.ts +3 -2
- package/dist/src/http/ip-middleware.d.ts.map +1 -1
- package/dist/src/http/ip-middleware.js +7 -4
- package/dist/src/http/ip-middleware.js.map +1 -1
- package/dist/src/http/params.d.ts +3 -3
- package/dist/src/http/params.d.ts.map +1 -1
- package/dist/src/http/params.js +2 -2
- package/dist/src/http/params.js.map +1 -1
- package/dist/src/http/port-retry.d.ts +18 -0
- package/dist/src/http/port-retry.d.ts.map +1 -0
- package/dist/src/http/port-retry.js +43 -0
- package/dist/src/http/port-retry.js.map +1 -0
- package/dist/src/http/router.d.ts +10 -7
- package/dist/src/http/router.d.ts.map +1 -1
- package/dist/src/http/router.js +184 -63
- package/dist/src/http/router.js.map +1 -1
- package/dist/src/http/schema.d.ts +5 -0
- package/dist/src/http/schema.d.ts.map +1 -0
- package/dist/src/http/schema.js +18 -0
- package/dist/src/http/schema.js.map +1 -0
- package/dist/src/http/server-factory-node.d.ts.map +1 -1
- package/dist/src/http/server-factory-node.js +1 -1
- package/dist/src/http/server-factory-node.js.map +1 -1
- package/dist/src/http/server-factory.d.ts +3 -3
- package/dist/src/http/server-factory.d.ts.map +1 -1
- package/dist/src/http/server-factory.js +1 -1
- package/dist/src/http/server-factory.js.map +1 -1
- package/dist/src/http/telemetry-routes.d.ts +5 -0
- package/dist/src/http/telemetry-routes.d.ts.map +1 -0
- package/dist/src/http/telemetry-routes.js +220 -0
- package/dist/src/http/telemetry-routes.js.map +1 -0
- package/dist/src/index.d.ts +18 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +14 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/migrator/dialect-sqlite.d.ts.map +1 -1
- package/dist/src/migrator/dialect-sqlite.js +7 -1
- package/dist/src/migrator/dialect-sqlite.js.map +1 -1
- package/dist/src/migrator/index.d.ts +4 -4
- package/dist/src/migrator/index.d.ts.map +1 -1
- package/dist/src/migrator/index.js +2 -2
- package/dist/src/migrator/index.js.map +1 -1
- package/dist/src/migrator/migrator.d.ts +8 -8
- package/dist/src/migrator/migrator.d.ts.map +1 -1
- package/dist/src/migrator/migrator.js +16 -13
- package/dist/src/migrator/migrator.js.map +1 -1
- package/dist/src/migrator/schema-diff.d.ts +2 -2
- package/dist/src/migrator/schema-diff.d.ts.map +1 -1
- package/dist/src/migrator/schema-diff.js.map +1 -1
- package/dist/src/migrator/types.d.ts +5 -5
- package/dist/src/migrator/types.d.ts.map +1 -1
- package/dist/src/migrator/types.js +6 -7
- package/dist/src/migrator/types.js.map +1 -1
- package/dist/src/platforms/node.d.ts +1 -1
- package/dist/src/platforms/node.d.ts.map +1 -1
- package/dist/src/platforms/node.js +1 -1
- package/dist/src/platforms/node.js.map +1 -1
- package/dist/src/repository/id.d.ts +1 -1
- package/dist/src/repository/id.d.ts.map +1 -1
- package/dist/src/repository/id.js +15 -3
- package/dist/src/repository/id.js.map +1 -1
- package/dist/src/repository/repository.d.ts +3 -2
- package/dist/src/repository/repository.d.ts.map +1 -1
- package/dist/src/repository/repository.js +11 -7
- package/dist/src/repository/repository.js.map +1 -1
- package/dist/src/rule/compiler.d.ts.map +1 -1
- package/dist/src/rule/compiler.js +2 -0
- package/dist/src/rule/compiler.js.map +1 -1
- package/dist/src/rule/enforcer.d.ts.map +1 -1
- package/dist/src/rule/enforcer.js.map +1 -1
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.d.ts.map +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/index.js.map +1 -1
- package/dist/src/storage/local-preset.d.ts.map +1 -1
- package/dist/src/storage/local-preset.js.map +1 -1
- package/dist/src/storage/storage.d.ts +2 -2
- package/dist/src/storage/storage.d.ts.map +1 -1
- package/dist/src/storage/storage.js +8 -26
- package/dist/src/storage/storage.js.map +1 -1
- package/dist/src/telemetry/collections.d.ts +4 -0
- package/dist/src/telemetry/collections.d.ts.map +1 -0
- package/dist/src/telemetry/collections.js +19 -0
- package/dist/src/telemetry/collections.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +17 -3
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +15 -5
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/logger.d.ts +29 -2
- package/dist/src/telemetry/logger.d.ts.map +1 -1
- package/dist/src/telemetry/logger.js +52 -15
- package/dist/src/telemetry/logger.js.map +1 -1
- package/dist/src/telemetry/sqlite-logger.d.ts +36 -0
- package/dist/src/telemetry/sqlite-logger.d.ts.map +1 -0
- package/dist/src/telemetry/sqlite-logger.js +193 -0
- package/dist/src/telemetry/sqlite-logger.js.map +1 -0
- package/dist/src/telemetry/telemetry-sql-client.d.ts +11 -0
- package/dist/src/telemetry/telemetry-sql-client.d.ts.map +1 -0
- package/dist/src/telemetry/telemetry-sql-client.js +8 -0
- package/dist/src/telemetry/telemetry-sql-client.js.map +1 -0
- package/dist/src/telemetry/tracer.d.ts +16 -1
- package/dist/src/telemetry/tracer.d.ts.map +1 -1
- package/dist/src/telemetry/tracer.js +87 -70
- package/dist/src/telemetry/tracer.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +11 -0
- package/dist/src/telemetry/types.d.ts.map +1 -0
- package/dist/src/telemetry/types.js +2 -0
- package/dist/src/telemetry/types.js.map +1 -0
- package/dist/src/thumbnail/index.d.ts +2 -2
- package/dist/src/thumbnail/index.d.ts.map +1 -1
- package/dist/src/thumbnail/index.js +1 -1
- package/dist/src/thumbnail/index.js.map +1 -1
- package/dist/src/thumbnail/photon.d.ts.map +1 -1
- package/dist/src/thumbnail/photon.js +1 -1
- package/dist/src/thumbnail/photon.js.map +1 -1
- package/dist/src/thumbnail/types.d.ts +1 -1
- package/dist/src/thumbnail/types.d.ts.map +1 -1
- package/dist/src/thumbnail/types.js +2 -2
- package/dist/src/thumbnail/types.js.map +1 -1
- package/dist/tests/app/app.test.js +101 -26
- package/dist/tests/app/app.test.js.map +1 -1
- package/dist/tests/app/builder.test.js +77 -9
- package/dist/tests/app/builder.test.js.map +1 -1
- package/dist/tests/app/define-rule.test.d.ts +2 -0
- package/dist/tests/app/define-rule.test.d.ts.map +1 -0
- package/dist/tests/app/define-rule.test.js +147 -0
- package/dist/tests/app/define-rule.test.js.map +1 -0
- package/dist/tests/cache/cached-collection.test.js +200 -40
- package/dist/tests/cache/cached-collection.test.js.map +1 -1
- package/dist/tests/cache/collection-cache.test.js +1 -1
- package/dist/tests/cache/collection-cache.test.js.map +1 -1
- package/dist/tests/collection-service/auth-credentials.test.d.ts +2 -0
- package/dist/tests/collection-service/auth-credentials.test.d.ts.map +1 -0
- package/dist/tests/collection-service/auth-credentials.test.js +172 -0
- package/dist/tests/collection-service/auth-credentials.test.js.map +1 -0
- package/dist/tests/collection-service/collection-service.test.js +374 -174
- package/dist/tests/collection-service/collection-service.test.js.map +1 -1
- package/dist/tests/collection-service/decode.test.d.ts +2 -0
- package/dist/tests/collection-service/decode.test.d.ts.map +1 -0
- package/dist/tests/collection-service/decode.test.js +37 -0
- package/dist/tests/collection-service/decode.test.js.map +1 -0
- package/dist/tests/collection-service/input-schema.test.js +162 -95
- package/dist/tests/collection-service/input-schema.test.js.map +1 -1
- package/dist/tests/config/config.test.js +15 -4
- package/dist/tests/config/config.test.js.map +1 -1
- package/dist/tests/cron/cron-integration.test.d.ts +2 -0
- package/dist/tests/cron/cron-integration.test.d.ts.map +1 -0
- package/dist/tests/cron/cron-integration.test.js +109 -0
- package/dist/tests/cron/cron-integration.test.js.map +1 -0
- package/dist/tests/cron/cron-persistence.test.d.ts +2 -0
- package/dist/tests/cron/cron-persistence.test.d.ts.map +1 -0
- package/dist/tests/cron/cron-persistence.test.js +123 -0
- package/dist/tests/cron/cron-persistence.test.js.map +1 -0
- package/dist/tests/cron/cron-service.test.d.ts +2 -0
- package/dist/tests/cron/cron-service.test.d.ts.map +1 -0
- package/dist/tests/cron/cron-service.test.js +124 -0
- package/dist/tests/cron/cron-service.test.js.map +1 -0
- package/dist/tests/crypto/crypto.test.js +2 -2
- package/dist/tests/crypto/crypto.test.js.map +1 -1
- package/dist/tests/hooks/hook-collection.test.d.ts +2 -0
- package/dist/tests/hooks/hook-collection.test.d.ts.map +1 -0
- package/dist/tests/hooks/hook-collection.test.js +110 -0
- package/dist/tests/hooks/hook-collection.test.js.map +1 -0
- package/dist/tests/hooks/hook-service.test.d.ts +2 -0
- package/dist/tests/hooks/hook-service.test.d.ts.map +1 -0
- package/dist/tests/hooks/hook-service.test.js +220 -0
- package/dist/tests/hooks/hook-service.test.js.map +1 -0
- package/dist/tests/http/cors.test.d.ts +2 -0
- package/dist/tests/http/cors.test.d.ts.map +1 -0
- package/dist/tests/http/cors.test.js +211 -0
- package/dist/tests/http/cors.test.js.map +1 -0
- package/dist/tests/http/file-serve.test.js +63 -54
- package/dist/tests/http/file-serve.test.js.map +1 -1
- package/dist/tests/http/file-token.test.js +69 -51
- package/dist/tests/http/file-token.test.js.map +1 -1
- package/dist/tests/http/ip-middleware.test.js +13 -13
- package/dist/tests/http/ip-middleware.test.js.map +1 -1
- package/dist/tests/http/port-retry.test.d.ts +2 -0
- package/dist/tests/http/port-retry.test.d.ts.map +1 -0
- package/dist/tests/http/port-retry.test.js +52 -0
- package/dist/tests/http/port-retry.test.js.map +1 -0
- package/dist/tests/http/router.test.js +260 -85
- package/dist/tests/http/router.test.js.map +1 -1
- package/dist/tests/migrator/dialect-sqlite.test.js +1 -1
- package/dist/tests/migrator/dialect-sqlite.test.js.map +1 -1
- package/dist/tests/migrator/integration.test.js +6 -6
- package/dist/tests/migrator/integration.test.js.map +1 -1
- package/dist/tests/migrator/migrator.test.js +5 -5
- package/dist/tests/migrator/migrator.test.js.map +1 -1
- package/dist/tests/migrator/schema-diff.test.js.map +1 -1
- package/dist/tests/repository/repository.test.js +71 -8
- package/dist/tests/repository/repository.test.js.map +1 -1
- package/dist/tests/rule/compiler.test.js +1 -1
- package/dist/tests/rule/compiler.test.js.map +1 -1
- package/dist/tests/rule/enforcer.test.js +9 -9
- package/dist/tests/rule/enforcer.test.js.map +1 -1
- package/dist/tests/storage/storage.test.js +15 -15
- package/dist/tests/storage/storage.test.js.map +1 -1
- package/dist/tests/telemetry/collections.test.d.ts +2 -0
- package/dist/tests/telemetry/collections.test.d.ts.map +1 -0
- package/dist/tests/telemetry/collections.test.js +67 -0
- package/dist/tests/telemetry/collections.test.js.map +1 -0
- package/dist/tests/telemetry/instrumentation.test.js +27 -20
- package/dist/tests/telemetry/instrumentation.test.js.map +1 -1
- package/dist/tests/telemetry/logger.test.js +38 -6
- package/dist/tests/telemetry/logger.test.js.map +1 -1
- package/dist/tests/telemetry/sqlite-logger.test.d.ts +2 -0
- package/dist/tests/telemetry/sqlite-logger.test.d.ts.map +1 -0
- package/dist/tests/telemetry/sqlite-logger.test.js +272 -0
- package/dist/tests/telemetry/sqlite-logger.test.js.map +1 -0
- package/dist/tests/telemetry/telemetry-routes.test.d.ts +2 -0
- package/dist/tests/telemetry/telemetry-routes.test.d.ts.map +1 -0
- package/dist/tests/telemetry/telemetry-routes.test.js +154 -0
- package/dist/tests/telemetry/telemetry-routes.test.js.map +1 -0
- package/dist/tests/telemetry/tracer.test.js +41 -4
- package/dist/tests/telemetry/tracer.test.js.map +1 -1
- package/dist/tests/thumbnail/photon.test.js +35 -35
- package/dist/tests/thumbnail/photon.test.js.map +1 -1
- package/dist/tests/thumbnail/thumbnail.test.js +7 -7
- package/dist/tests/thumbnail/thumbnail.test.js.map +1 -1
- package/package.json +9 -9
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AuthCollection, BaseCollection, Field } from "@gettersethya/mira-client";
|
|
2
2
|
import { Layer } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
4
|
import { MiraApp } from "../../src/app/app.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { MiraBuilder } from "../../src/app/builder.js";
|
|
6
|
+
import { defineRule } from "../../src/app/index.js";
|
|
7
|
+
import { MiraPlugin } from "../../src/app/plugin.js";
|
|
7
8
|
import { SqliteDatabase } from "../../src/databases/sqlite.js";
|
|
9
|
+
import { defaultCorsConfig } from "../../src/http/cors.js";
|
|
10
|
+
import { NodePlatform } from "../../src/platforms/node.js";
|
|
8
11
|
import { LocalFileStorage } from "../../src/storage/index.js";
|
|
12
|
+
import { ConsoleTelemetryLayer } from "../../src/telemetry/index.js";
|
|
9
13
|
// Use real presets as stubs — builder stores them without invoking them in unit tests
|
|
10
14
|
const stubPlatform = NodePlatform;
|
|
11
15
|
const stubDatabase = SqliteDatabase({ filename: ":memory:" });
|
|
@@ -55,15 +59,79 @@ describe("MiraBuilder", () => {
|
|
|
55
59
|
describe("MiraApp.extend()", () => {
|
|
56
60
|
it("returns this (chainable)", () => {
|
|
57
61
|
const app = fullBuilder().build();
|
|
58
|
-
const result = app.extend(Layer.empty);
|
|
62
|
+
const result = app.extend(MiraPlugin.fromLayer(Layer.empty));
|
|
59
63
|
expect(result).toBe(app);
|
|
60
64
|
});
|
|
61
65
|
it("accumulates extras in order", () => {
|
|
62
|
-
const
|
|
63
|
-
const
|
|
66
|
+
const pluginA = MiraPlugin.fromLayer(Layer.empty);
|
|
67
|
+
const pluginB = MiraPlugin.fromLayer(Layer.empty);
|
|
68
|
+
const app = fullBuilder().build();
|
|
69
|
+
app.extend(pluginA).extend(pluginB);
|
|
70
|
+
expect(app._getExtras()).toEqual([pluginA, pluginB]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("MiraBuilder.rules()", () => {
|
|
74
|
+
const postsDef = BaseCollection.define("posts", { title: Field.text() });
|
|
75
|
+
it("stores rules on builder config", () => {
|
|
76
|
+
const rb = defineRule(postsDef, (R) => ({ list: R.public() }));
|
|
77
|
+
const b = fullBuilder().rules([rb]);
|
|
78
|
+
expect(b._getPartialConfig().rules).toEqual([rb]);
|
|
79
|
+
});
|
|
80
|
+
it("throws on duplicate collection names", () => {
|
|
81
|
+
const rb1 = defineRule(postsDef, (R) => ({ list: R.public() }));
|
|
82
|
+
const rb2 = defineRule(postsDef, (R) => ({ view: R.public() }));
|
|
83
|
+
expect(() => fullBuilder().rules([rb1, rb2])).toThrow('Duplicate rule binding for collection "posts"');
|
|
84
|
+
});
|
|
85
|
+
it("rules survive through build", () => {
|
|
86
|
+
const rb = defineRule(postsDef, (R) => ({ list: R.public() }));
|
|
87
|
+
const app = fullBuilder().rules([rb]).build();
|
|
88
|
+
expect(app._getConfig().rules).toEqual([rb]);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe("MiraBuilder.cors()", () => {
|
|
92
|
+
it("stores cors on builder config", () => {
|
|
93
|
+
const cors = { allowedOrigins: ["http://localhost:3000"] };
|
|
94
|
+
const b = fullBuilder().cors(cors);
|
|
95
|
+
expect(b._getPartialConfig().cors).toEqual(cors);
|
|
96
|
+
});
|
|
97
|
+
it("defaults cors to defaultCorsConfig when .cors() not called", () => {
|
|
98
|
+
const app = fullBuilder().build();
|
|
99
|
+
expect(app._getConfig().cors).toBe(defaultCorsConfig);
|
|
100
|
+
});
|
|
101
|
+
it("custom cors survives through build", () => {
|
|
102
|
+
const cors = { allowedOrigins: ["http://localhost:3000"], credentials: true };
|
|
103
|
+
const app = fullBuilder().cors(cors).build();
|
|
104
|
+
expect(app._getConfig().cors).toEqual(cors);
|
|
105
|
+
});
|
|
106
|
+
it("throws on wildcard origin with credentials", () => {
|
|
107
|
+
expect(() => fullBuilder().cors({ allowedOrigins: ["*"], credentials: true })).toThrow("credentials: true requires explicit allowedOrigins");
|
|
108
|
+
});
|
|
109
|
+
it("throws on omitted origins with credentials", () => {
|
|
110
|
+
expect(() => fullBuilder().cors({ credentials: true })).toThrow("credentials: true requires explicit allowedOrigins");
|
|
111
|
+
});
|
|
112
|
+
it("allows explicit origin with credentials", () => {
|
|
113
|
+
const b = fullBuilder().cors({ allowedOrigins: ["http://localhost:3000"], credentials: true });
|
|
114
|
+
expect(b._getPartialConfig().cors).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("MiraPlugin.adminCollections", () => {
|
|
118
|
+
const adminDef = AuthCollection.define("_admin", {});
|
|
119
|
+
it("auto-adds plugin admin collections to the app collection set", () => {
|
|
120
|
+
const plugin = MiraPlugin.define({ adminCollections: [adminDef] });
|
|
121
|
+
const app = fullBuilder().build().extend(plugin);
|
|
122
|
+
expect(app._resolveAdminCollections()).toEqual([adminDef]);
|
|
123
|
+
});
|
|
124
|
+
it("merges admin collections from multiple plugins", () => {
|
|
125
|
+
const otherDef = AuthCollection.define("_otheradmin", {});
|
|
126
|
+
const app = fullBuilder()
|
|
127
|
+
.build()
|
|
128
|
+
.extend(MiraPlugin.define({ adminCollections: [adminDef] }))
|
|
129
|
+
.extend(MiraPlugin.define({ adminCollections: [otherDef] }));
|
|
130
|
+
expect(app._resolveAdminCollections()).toEqual([adminDef, otherDef]);
|
|
131
|
+
});
|
|
132
|
+
it("there is no admin bypass when no plugin declares one", () => {
|
|
64
133
|
const app = fullBuilder().build();
|
|
65
|
-
app.
|
|
66
|
-
expect(app._getExtras()).toEqual([layerA, layerB]);
|
|
134
|
+
expect(app._resolveAdminCollections()).toEqual([]);
|
|
67
135
|
});
|
|
68
136
|
});
|
|
69
137
|
//# sourceMappingURL=builder.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.test.js","sourceRoot":"","sources":["../../../tests/app/builder.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"builder.test.js","sourceRoot":"","sources":["../../../tests/app/builder.test.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAA;AACjF,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAC,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE5C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE5D,sFAAsF;AACtF,MAAM,YAAY,GAAG,YAAY,CAAA;AACjC,MAAM,YAAY,GAAG,cAAc,CAAC,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAA;AAC7D,MAAM,WAAW,GAAG,gBAAgB,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAA;AACjE,MAAM,eAAe,GAAoC,EAAE,CAAA;AAE3D,MAAM,WAAW,GAAG,GAAG,EAAE,CACvB,IAAI,WAAW,EAAE;KACd,QAAQ,CAAC,YAAY,CAAC;KACtB,QAAQ,CAAC,YAAY,CAAC;KACtB,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,eAAe,CAAC,CAAA;AAEjC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;QAClD,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;QAClD,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;QAChD,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC,WAAW,CAAC,eAAe,CAAC,CAAA;QACxD,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IACjE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,EAAE,CAAA;QACjC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,EAAE,CAAA;QACjC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,CAAA;QACnC,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC,KAAK,EAAE,CAAA;QAC5D,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,EAAE,GAAG,IAAI,WAAW,EAAE,CAAA;QAC5B,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;QACpC,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,EAAE,CAAA;QACjC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAA;QAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,OAAO,GAAG,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACjD,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,EAAE,CAAA;QACjC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACnC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;IAExE,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,EAAE,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;QAC9D,MAAM,CAAC,GAAG,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QACnC,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;QAC/D,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;QAC/D,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CACnD,+CAA+C,CAChD,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,EAAE,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAA;QAC9D,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;QAC7C,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,IAAI,GAAG,EAAE,cAAc,EAAE,CAAC,uBAAuB,CAAC,EAAE,CAAA;QAC1D,MAAM,CAAC,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClC,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,EAAE,CAAA;QACjC,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,IAAI,GAAG,EAAE,cAAc,EAAE,CAAC,uBAAuB,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;QAC7E,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAA;QAC5C,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CACpF,oDAAoD,CACrD,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAC7D,oDAAoD,CACrD,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,CAAC,uBAAuB,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9F,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;IAEpD,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QAClE,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChD,MAAM,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;QACzD,MAAM,GAAG,GAAG,WAAW,EAAE;aACtB,KAAK,EAAE;aACP,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;aAC3D,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;QAC9D,MAAM,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC,KAAK,EAAE,CAAA;QACjC,MAAM,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-rule.test.d.ts","sourceRoot":"","sources":["../../../tests/app/define-rule.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { AuthCollection, BaseCollection, Field, ViewCollection } from "@gettersethya/mira-client";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { applyRulesToCollections, defineRule } from "../../src/app/index.js";
|
|
4
|
+
const Posts = BaseCollection.define("posts", {
|
|
5
|
+
title: Field.text({ maxLength: 200 }),
|
|
6
|
+
});
|
|
7
|
+
const Users = AuthCollection.define("users", {
|
|
8
|
+
name: Field.text(),
|
|
9
|
+
});
|
|
10
|
+
const ActivePosts = ViewCollection.define("active_posts", "SELECT id, seqId, title FROM posts WHERE status = 'active'", {
|
|
11
|
+
id: Field.text().view(),
|
|
12
|
+
seqId: Field.integer().view(),
|
|
13
|
+
title: Field.text().view(),
|
|
14
|
+
});
|
|
15
|
+
describe("defineRule", () => {
|
|
16
|
+
it("returns RuleBinding with correct collectionName", () => {
|
|
17
|
+
const rb = defineRule(Posts, (R) => ({ list: R.public() }));
|
|
18
|
+
expect(rb._tag).toBe("RuleBinding");
|
|
19
|
+
expect(rb.collectionName).toBe("posts");
|
|
20
|
+
expect(rb.ruleMap).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
it("ruleMap contains the rules returned by callback", () => {
|
|
23
|
+
const rb = defineRule(Posts, (R) => ({
|
|
24
|
+
list: R.public(),
|
|
25
|
+
view: R.field("title").eq(R.literal("hello")),
|
|
26
|
+
}));
|
|
27
|
+
expect(rb.ruleMap.list).toEqual({ op: "public" });
|
|
28
|
+
expect(rb.ruleMap.view).toEqual({
|
|
29
|
+
op: "eq",
|
|
30
|
+
left: { kind: "field", field: "title" },
|
|
31
|
+
right: { kind: "literal", value: "hello" },
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it("callback receives RuleBuilder with collection fields", () => {
|
|
35
|
+
const rb = defineRule(Posts, (R) => {
|
|
36
|
+
const op = R.field("title");
|
|
37
|
+
expect(op).toMatchObject({ kind: "field", field: "title" });
|
|
38
|
+
return {};
|
|
39
|
+
});
|
|
40
|
+
expect(rb._tag).toBe("RuleBinding");
|
|
41
|
+
});
|
|
42
|
+
it("callback receives system fields on base collection", () => {
|
|
43
|
+
defineRule(Posts, (R) => {
|
|
44
|
+
expect(R.field("id")).toMatchObject({ kind: "field", field: "id" });
|
|
45
|
+
expect(R.field("created")).toMatchObject({ kind: "field", field: "created" });
|
|
46
|
+
expect(R.field("updated")).toMatchObject({ kind: "field", field: "updated" });
|
|
47
|
+
expect(R.field("seqId")).toMatchObject({ kind: "field", field: "seqId" });
|
|
48
|
+
return {};
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it("callback receives auth system fields on auth collection", () => {
|
|
52
|
+
defineRule(Users, (R) => {
|
|
53
|
+
expect(R.field("email")).toMatchObject({ kind: "field", field: "email" });
|
|
54
|
+
expect(R.field("emailVerified")).toMatchObject({ kind: "field", field: "emailVerified" });
|
|
55
|
+
return {};
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
it("selfId() available for auth collections", () => {
|
|
59
|
+
defineRule(Users, (R) => {
|
|
60
|
+
expect(R.selfId()).toMatchObject({ kind: "authId", collection: "users" });
|
|
61
|
+
return {};
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it("empty callback returns empty RuleMap", () => {
|
|
65
|
+
const rb = defineRule(Posts, () => ({}));
|
|
66
|
+
expect(rb.ruleMap).toEqual({});
|
|
67
|
+
});
|
|
68
|
+
it("view collection gets its declared fields", () => {
|
|
69
|
+
defineRule(ActivePosts, (R) => {
|
|
70
|
+
const idOp = R.field("id");
|
|
71
|
+
const seqIdOp = R.field("seqId");
|
|
72
|
+
const titleOp = R.field("title");
|
|
73
|
+
expect(idOp.kind).toBe("field");
|
|
74
|
+
expect(seqIdOp.kind).toBe("field");
|
|
75
|
+
expect(titleOp.kind).toBe("field");
|
|
76
|
+
return {};
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
it("view collection rule map keeps list and view only", () => {
|
|
80
|
+
const rb = defineRule(ActivePosts, (R) => ({
|
|
81
|
+
list: R.public(),
|
|
82
|
+
view: R.field("title").eq(R.literal("x")),
|
|
83
|
+
}));
|
|
84
|
+
expect(rb._tag).toBe("RuleBinding");
|
|
85
|
+
expect(rb.collectionName).toBe("active_posts");
|
|
86
|
+
expect(rb.ruleMap.list).toEqual({ op: "public" });
|
|
87
|
+
expect(rb.ruleMap.view).toEqual({
|
|
88
|
+
op: "eq",
|
|
89
|
+
left: { kind: "field", field: "title" },
|
|
90
|
+
right: { kind: "literal", value: "x" },
|
|
91
|
+
});
|
|
92
|
+
expect(rb.ruleMap.create).toBeUndefined();
|
|
93
|
+
expect(rb.ruleMap.update).toBeUndefined();
|
|
94
|
+
expect(rb.ruleMap.delete).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
it("auth collection rule map supports all CRUD actions", () => {
|
|
97
|
+
const rb = defineRule(Users, (R) => ({
|
|
98
|
+
list: R.public(),
|
|
99
|
+
view: R.selfId().eq(R.literal("u1")),
|
|
100
|
+
create: R.public(),
|
|
101
|
+
update: R.selfId().eq(R.literal("u1")),
|
|
102
|
+
delete: R.selfId().eq(R.literal("u1")),
|
|
103
|
+
}));
|
|
104
|
+
expect(rb._tag).toBe("RuleBinding");
|
|
105
|
+
expect(rb.collectionName).toBe("users");
|
|
106
|
+
expect(rb.ruleMap.list).toEqual({ op: "public" });
|
|
107
|
+
expect(rb.ruleMap.view).toEqual({
|
|
108
|
+
op: "eq",
|
|
109
|
+
left: { kind: "authId", collection: "users" },
|
|
110
|
+
right: { kind: "literal", value: "u1" },
|
|
111
|
+
});
|
|
112
|
+
expect(rb.ruleMap.create).toEqual({ op: "public" });
|
|
113
|
+
expect(rb.ruleMap.update).toBeDefined();
|
|
114
|
+
expect(rb.ruleMap.delete).toBeDefined();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe("applyRulesToCollections", () => {
|
|
118
|
+
it("merges rules into matching collection schema", () => {
|
|
119
|
+
const rb = defineRule(Posts, (R) => ({ list: R.public() }));
|
|
120
|
+
const [merged] = applyRulesToCollections([Posts], [rb]);
|
|
121
|
+
expect(merged.schema["x-rules"]).toBeDefined();
|
|
122
|
+
expect(merged.schema["x-rules"].list).toEqual({ op: "public" });
|
|
123
|
+
});
|
|
124
|
+
it("collection without RuleBinding has no x-rules", () => {
|
|
125
|
+
const merged = applyRulesToCollections([Posts, Users], []);
|
|
126
|
+
expect(merged[0].schema).not.toHaveProperty("x-rules");
|
|
127
|
+
expect(merged[1].schema).not.toHaveProperty("x-rules");
|
|
128
|
+
});
|
|
129
|
+
it("no rules array at all — all collections have no x-rules", () => {
|
|
130
|
+
const merged = applyRulesToCollections([Posts, Users], []);
|
|
131
|
+
for (const c of merged) {
|
|
132
|
+
expect(c.schema).not.toHaveProperty("x-rules");
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
it("RuleBinding for unknown collection name throws", () => {
|
|
136
|
+
const rb = defineRule(Posts, (R) => ({ list: R.public() }));
|
|
137
|
+
const unknown = { ...rb, collectionName: "nonexistent" };
|
|
138
|
+
expect(() => applyRulesToCollections([Posts], [unknown])).toThrow('RuleBinding references unknown collection "nonexistent"');
|
|
139
|
+
});
|
|
140
|
+
it("returns a new array with original collections unmodified", () => {
|
|
141
|
+
const rb = defineRule(Posts, (R) => ({ list: R.public() }));
|
|
142
|
+
const merged = applyRulesToCollections([Posts], [rb]);
|
|
143
|
+
expect(merged).not.toBe([Posts]);
|
|
144
|
+
expect(Posts.schema).not.toHaveProperty("x-rules");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=define-rule.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-rule.test.js","sourceRoot":"","sources":["../../../tests/app/define-rule.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import { SqlClient } from "@effect/sql";
|
|
2
|
-
import { SqliteClient } from "@effect/sql-sqlite-node";
|
|
3
|
-
import { Effect, Either, Layer } from "effect";
|
|
4
1
|
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { SqliteClient } from "@effect/sql-sqlite-node";
|
|
5
3
|
import { describe, it } from "@effect/vitest";
|
|
6
|
-
import {
|
|
4
|
+
import { AuthCollection } from "@gettersethya/mira-client";
|
|
7
5
|
import { BaseCollection } from "@gettersethya/mira-client";
|
|
8
6
|
import { Field } from "@gettersethya/mira-client";
|
|
9
7
|
import { ViewCollection } from "@gettersethya/mira-client";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { Effect, Layer, Result } from "effect";
|
|
9
|
+
import { SqlClient } from "effect/sql";
|
|
10
|
+
import { expect } from "vitest";
|
|
11
|
+
import { applyRulesToCollections, defineRule } from "../../src/app/index.js";
|
|
12
12
|
import { makeCachedCollectionServiceLayer } from "../../src/cache/cached-collection.js";
|
|
13
|
+
import { CollectionService } from "../../src/collection-service/collection-service.js";
|
|
14
|
+
import { NodeCryptoLayer } from "../../src/crypto/node.js";
|
|
15
|
+
import { Dialect } from "../../src/dialect/dialect.js";
|
|
16
|
+
import { sqliteDialect } from "../../src/dialect/dialect-sqlite.js";
|
|
17
|
+
import { NodeAuthServiceLayer } from "../../src/http/auth-node.js";
|
|
13
18
|
import { RepositoryLive } from "../../src/repository/repository.js";
|
|
14
19
|
import { FileStorage, FileStorageNotFound } from "../../src/storage/storage.js";
|
|
15
|
-
|
|
16
|
-
const Posts = BaseCollection.define("posts", {
|
|
20
|
+
const postsDef = BaseCollection.define("posts", {
|
|
17
21
|
title: Field.text({ maxLength: 100 }),
|
|
18
|
-
})
|
|
22
|
+
});
|
|
23
|
+
const postsRules = defineRule(postsDef, (R) => ({
|
|
19
24
|
list: R.public(),
|
|
20
25
|
view: R.public(),
|
|
21
26
|
create: R.public(),
|
|
@@ -25,15 +30,63 @@ const Posts = BaseCollection.define("posts", {
|
|
|
25
30
|
// Intentionally slow view: 100 000-step recursive CTE forces SQLite to do
|
|
26
31
|
// significant work on every query, making the cache speedup clearly measurable.
|
|
27
32
|
// SUM(n) requires visiting all 100 000 rows — SQLite cannot short-circuit it.
|
|
28
|
-
const SLOW_SQL = `
|
|
29
|
-
WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
|
|
33
|
+
const SLOW_SQL = `
|
|
34
|
+
WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
|
|
30
35
|
SELECT 1 AS seqId, 'perf-test-id' AS id, CAST(SUM(n) AS TEXT) AS title FROM gen`;
|
|
31
|
-
const
|
|
36
|
+
const slowViewDef = ViewCollection.define("slow_posts", SLOW_SQL.trim(), {
|
|
32
37
|
seqId: Field.integer().view(),
|
|
33
38
|
id: Field.text().view(),
|
|
34
39
|
title: Field.text().view(),
|
|
35
|
-
})
|
|
40
|
+
});
|
|
41
|
+
const slowViewRules = defineRule(slowViewDef, (R) => ({ list: R.public(), view: R.public() }));
|
|
36
42
|
const noCtx = { headers: {}, query: {} };
|
|
43
|
+
const adminCtx = { headers: {}, query: {}, admin: true };
|
|
44
|
+
// Dummy collection — only its `.name` is used by Rule.authId(), never queried.
|
|
45
|
+
const usersDef = BaseCollection.define("users", {
|
|
46
|
+
name: Field.text({ maxLength: 100 }),
|
|
47
|
+
});
|
|
48
|
+
const usersRules = defineRule(usersDef, (R) => ({
|
|
49
|
+
list: R.public(),
|
|
50
|
+
view: R.public(),
|
|
51
|
+
create: R.public(),
|
|
52
|
+
update: R.public(),
|
|
53
|
+
delete: R.public(),
|
|
54
|
+
}));
|
|
55
|
+
const Users = applyRulesToCollections([usersDef], [usersRules])[0];
|
|
56
|
+
// Owner-only rule — scoped by the authenticated identity (@auth_id).
|
|
57
|
+
const notesDef = BaseCollection.define("notes", {
|
|
58
|
+
ownerId: Field.text({ maxLength: 100 }),
|
|
59
|
+
body: Field.text({ maxLength: 200 }),
|
|
60
|
+
});
|
|
61
|
+
const notesRules = defineRule(notesDef, (R) => ({
|
|
62
|
+
list: R.public(),
|
|
63
|
+
view: R.field("ownerId").eq(R.authId(Users)),
|
|
64
|
+
create: R.public(),
|
|
65
|
+
update: R.public(),
|
|
66
|
+
delete: R.public(),
|
|
67
|
+
}));
|
|
68
|
+
// Tenant-scoped rule — bound by an incoming request query param (@request_query_tenantId).
|
|
69
|
+
const tenantDocsDef = BaseCollection.define("tenant_docs", {
|
|
70
|
+
tenantId: Field.text({ maxLength: 100 }),
|
|
71
|
+
body: Field.text({ maxLength: 200 }),
|
|
72
|
+
});
|
|
73
|
+
const tenantDocsRules = defineRule(tenantDocsDef, (R) => ({
|
|
74
|
+
list: R.public(),
|
|
75
|
+
view: R.field("tenantId").eq(R.request("query", "tenantId")),
|
|
76
|
+
create: R.public(),
|
|
77
|
+
update: R.public(),
|
|
78
|
+
delete: R.public(),
|
|
79
|
+
}));
|
|
80
|
+
// Auth collection — `password` is x-hidden, stripped from non-admin responses only.
|
|
81
|
+
const accountsDef = AuthCollection.define("accounts", {});
|
|
82
|
+
const accountsRules = defineRule(accountsDef, (R) => ({
|
|
83
|
+
list: R.public(),
|
|
84
|
+
view: R.public(),
|
|
85
|
+
create: R.public(),
|
|
86
|
+
update: R.public(),
|
|
87
|
+
delete: R.public(),
|
|
88
|
+
}));
|
|
89
|
+
const [Posts, SlowView, Notes, TenantDocs, Accounts] = applyRulesToCollections([postsDef, slowViewDef, notesDef, tenantDocsDef, accountsDef], [postsRules, slowViewRules, notesRules, tenantDocsRules, accountsRules]);
|
|
37
90
|
const FileStorageTest = Layer.succeed(FileStorage, FileStorage.of({
|
|
38
91
|
upload: (key) => Effect.succeed(key),
|
|
39
92
|
delete: () => Effect.void,
|
|
@@ -44,12 +97,13 @@ const FileStorageTest = Layer.succeed(FileStorage, FileStorage.of({
|
|
|
44
97
|
}));
|
|
45
98
|
const sqliteLayer = SqliteClient.layer({ filename: ":memory:" });
|
|
46
99
|
const perfSqliteLayer = SqliteClient.layer({ filename: ":memory:" });
|
|
100
|
+
const DialectTest = Layer.succeed(Dialect, sqliteDialect);
|
|
47
101
|
const cachedServiceWithDeps = makeCachedCollectionServiceLayer([Posts], {
|
|
48
102
|
recordTtlMs: 60_000,
|
|
49
103
|
listTtlMs: 60_000,
|
|
50
104
|
maxRecords: 100,
|
|
51
105
|
maxLists: 100,
|
|
52
|
-
}).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
|
|
106
|
+
}).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
|
|
53
107
|
// Expose CollectionService AND SqlClient so setupPostsTable can access SqlClient.SqlClient
|
|
54
108
|
const testLayer = Layer.mergeAll(cachedServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
|
|
55
109
|
const perfCachedServiceWithDeps = makeCachedCollectionServiceLayer([Posts, SlowView], {
|
|
@@ -57,40 +111,91 @@ const perfCachedServiceWithDeps = makeCachedCollectionServiceLayer([Posts, SlowV
|
|
|
57
111
|
listTtlMs: 60_000,
|
|
58
112
|
maxRecords: 100,
|
|
59
113
|
maxLists: 100,
|
|
60
|
-
}).pipe(Layer.provide(RepositoryLive), Layer.provide(perfSqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
|
|
114
|
+
}).pipe(Layer.provide(RepositoryLive), Layer.provide(perfSqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
|
|
61
115
|
const perfTestLayer = Layer.mergeAll(perfCachedServiceWithDeps, perfSqliteLayer, FileStorageTest, NodeCryptoLayer);
|
|
116
|
+
const securitySqliteLayer = SqliteClient.layer({ filename: ":memory:" });
|
|
117
|
+
const securityCachedServiceWithDeps = makeCachedCollectionServiceLayer([Notes, TenantDocs, Accounts], {
|
|
118
|
+
recordTtlMs: 60_000,
|
|
119
|
+
listTtlMs: 60_000,
|
|
120
|
+
maxRecords: 100,
|
|
121
|
+
maxLists: 100,
|
|
122
|
+
}).pipe(Layer.provide(RepositoryLive), Layer.provide(securitySqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
|
|
123
|
+
const securityTestLayer = Layer.mergeAll(securityCachedServiceWithDeps, securitySqliteLayer, FileStorageTest, NodeCryptoLayer);
|
|
124
|
+
const setupNotesTable = Effect.gen(function* () {
|
|
125
|
+
const sql = yield* SqlClient.SqlClient;
|
|
126
|
+
yield* sql.unsafe(`
|
|
127
|
+
CREATE TABLE IF NOT EXISTS "notes" (
|
|
128
|
+
"seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
129
|
+
"id" TEXT NOT NULL UNIQUE,
|
|
130
|
+
"ownerId" TEXT NOT NULL,
|
|
131
|
+
"body" TEXT NOT NULL,
|
|
132
|
+
"created" TEXT NOT NULL,
|
|
133
|
+
"updated" TEXT NOT NULL
|
|
134
|
+
)
|
|
135
|
+
`);
|
|
136
|
+
yield* sql.unsafe(`DELETE FROM "notes"`);
|
|
137
|
+
});
|
|
138
|
+
const setupTenantDocsTable = Effect.gen(function* () {
|
|
139
|
+
const sql = yield* SqlClient.SqlClient;
|
|
140
|
+
yield* sql.unsafe(`
|
|
141
|
+
CREATE TABLE IF NOT EXISTS "tenant_docs" (
|
|
142
|
+
"seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
143
|
+
"id" TEXT NOT NULL UNIQUE,
|
|
144
|
+
"tenantId" TEXT NOT NULL,
|
|
145
|
+
"body" TEXT NOT NULL,
|
|
146
|
+
"created" TEXT NOT NULL,
|
|
147
|
+
"updated" TEXT NOT NULL
|
|
148
|
+
)
|
|
149
|
+
`);
|
|
150
|
+
yield* sql.unsafe(`DELETE FROM "tenant_docs"`);
|
|
151
|
+
});
|
|
152
|
+
const setupAccountsTable = Effect.gen(function* () {
|
|
153
|
+
const sql = yield* SqlClient.SqlClient;
|
|
154
|
+
yield* sql.unsafe(`
|
|
155
|
+
CREATE TABLE IF NOT EXISTS "accounts" (
|
|
156
|
+
"seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
157
|
+
"id" TEXT NOT NULL UNIQUE,
|
|
158
|
+
"email" TEXT NOT NULL UNIQUE,
|
|
159
|
+
"password" TEXT NOT NULL,
|
|
160
|
+
"emailVerified" INTEGER NOT NULL DEFAULT 0,
|
|
161
|
+
"created" TEXT NOT NULL,
|
|
162
|
+
"updated" TEXT NOT NULL
|
|
163
|
+
)
|
|
164
|
+
`);
|
|
165
|
+
yield* sql.unsafe(`DELETE FROM "accounts"`);
|
|
166
|
+
});
|
|
62
167
|
const setupPostsTable = Effect.gen(function* () {
|
|
63
168
|
const sql = yield* SqlClient.SqlClient;
|
|
64
|
-
yield* sql.unsafe(`
|
|
65
|
-
CREATE TABLE IF NOT EXISTS "posts" (
|
|
66
|
-
"seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
67
|
-
"id" TEXT NOT NULL UNIQUE,
|
|
68
|
-
"title" TEXT NOT NULL,
|
|
69
|
-
"created" TEXT NOT NULL,
|
|
70
|
-
"updated" TEXT NOT NULL
|
|
71
|
-
)
|
|
169
|
+
yield* sql.unsafe(`
|
|
170
|
+
CREATE TABLE IF NOT EXISTS "posts" (
|
|
171
|
+
"seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
172
|
+
"id" TEXT NOT NULL UNIQUE,
|
|
173
|
+
"title" TEXT NOT NULL,
|
|
174
|
+
"created" TEXT NOT NULL,
|
|
175
|
+
"updated" TEXT NOT NULL
|
|
176
|
+
)
|
|
72
177
|
`);
|
|
73
178
|
yield* sql.unsafe(`DELETE FROM "posts"`);
|
|
74
179
|
});
|
|
75
180
|
const setupPerfView = Effect.gen(function* () {
|
|
76
181
|
const sql = yield* SqlClient.SqlClient;
|
|
77
182
|
// posts table required by the layer even though SlowView doesn't query it
|
|
78
|
-
yield* sql.unsafe(`
|
|
79
|
-
CREATE TABLE IF NOT EXISTS "posts" (
|
|
80
|
-
"seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
|
-
"id" TEXT NOT NULL UNIQUE,
|
|
82
|
-
"title" TEXT NOT NULL,
|
|
83
|
-
"created" TEXT NOT NULL,
|
|
84
|
-
"updated" TEXT NOT NULL
|
|
85
|
-
)
|
|
183
|
+
yield* sql.unsafe(`
|
|
184
|
+
CREATE TABLE IF NOT EXISTS "posts" (
|
|
185
|
+
"seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
186
|
+
"id" TEXT NOT NULL UNIQUE,
|
|
187
|
+
"title" TEXT NOT NULL,
|
|
188
|
+
"created" TEXT NOT NULL,
|
|
189
|
+
"updated" TEXT NOT NULL
|
|
190
|
+
)
|
|
86
191
|
`);
|
|
87
192
|
yield* sql.unsafe(`DROP VIEW IF EXISTS "slow_posts"`);
|
|
88
193
|
// This view is intentionally bad: the 100 000-step CTE forces SQLite to do
|
|
89
194
|
// real work on every query execution. SUM(n) requires all rows — no early exit.
|
|
90
|
-
yield* sql.unsafe(`
|
|
91
|
-
CREATE VIEW "slow_posts" AS
|
|
92
|
-
WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
|
|
93
|
-
SELECT 1 AS seqId, 'perf-test-id' AS id, CAST(SUM(n) AS TEXT) AS title FROM gen
|
|
195
|
+
yield* sql.unsafe(`
|
|
196
|
+
CREATE VIEW "slow_posts" AS
|
|
197
|
+
WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
|
|
198
|
+
SELECT 1 AS seqId, 'perf-test-id' AS id, CAST(SUM(n) AS TEXT) AS title FROM gen
|
|
94
199
|
`);
|
|
95
200
|
});
|
|
96
201
|
describe("CachedCollectionService", () => {
|
|
@@ -132,10 +237,10 @@ describe("CachedCollectionService", () => {
|
|
|
132
237
|
// Delete via service — invalidates cache
|
|
133
238
|
yield* svc.delete(Posts, id, noCtx);
|
|
134
239
|
// View should fail with NotFoundError
|
|
135
|
-
const result = yield* Effect.
|
|
136
|
-
expect(
|
|
137
|
-
if (
|
|
138
|
-
expect(result.
|
|
240
|
+
const result = yield* Effect.result(svc.view(Posts, id, noCtx));
|
|
241
|
+
expect(Result.isFailure(result)).toBe(true);
|
|
242
|
+
if (Result.isFailure(result)) {
|
|
243
|
+
expect(result.failure._tag).toBe("NotFoundError");
|
|
139
244
|
}
|
|
140
245
|
}).pipe(Effect.provide(testLayer)));
|
|
141
246
|
it.effect("list cache hit — second list returns cached count after direct DB insert", () => Effect.gen(function* () {
|
|
@@ -184,7 +289,7 @@ describe("CachedCollectionService", () => {
|
|
|
184
289
|
it.effect("list cache invalidated by delete — record no longer in list", () => Effect.gen(function* () {
|
|
185
290
|
yield* setupPostsTable;
|
|
186
291
|
const svc = yield* CollectionService;
|
|
187
|
-
|
|
292
|
+
yield* svc.create(Posts, { title: "Keep" }, noCtx);
|
|
188
293
|
const r2 = yield* svc.create(Posts, { title: "Remove" }, noCtx);
|
|
189
294
|
const id2 = r2["id"];
|
|
190
295
|
// Prime the list cache
|
|
@@ -200,6 +305,61 @@ describe("CachedCollectionService", () => {
|
|
|
200
305
|
expect(titles).not.toContain("Remove");
|
|
201
306
|
}).pipe(Effect.provide(testLayer)));
|
|
202
307
|
});
|
|
308
|
+
describe("Cache key authorization scoping (security)", () => {
|
|
309
|
+
it.effect("auth-identity-bound rule — cache must not leak a record across different owners", () => Effect.gen(function* () {
|
|
310
|
+
yield* setupNotesTable;
|
|
311
|
+
const svc = yield* CollectionService;
|
|
312
|
+
const created = yield* svc.create(Notes, { ownerId: "userA", body: "secret" }, noCtx);
|
|
313
|
+
const id = created["id"];
|
|
314
|
+
const ctxOwner = { headers: {}, query: {}, auth: { collection: "users", record: { id: "userA" } } };
|
|
315
|
+
const ctxOther = { headers: {}, query: {}, auth: { collection: "users", record: { id: "userB" } } };
|
|
316
|
+
// Owner views their own note — populates the cache
|
|
317
|
+
const ownerView = yield* svc.view(Notes, id, ctxOwner);
|
|
318
|
+
expect(ownerView["body"]).toBe("secret");
|
|
319
|
+
// A different identity requests the same id/select/expand within the TTL window.
|
|
320
|
+
// The rule (ownerId == @auth_id) denies userB at the DB layer — a cache hit must
|
|
321
|
+
// not bypass that check and hand back userA's row.
|
|
322
|
+
const otherView = yield* Effect.result(svc.view(Notes, id, ctxOther));
|
|
323
|
+
expect(Result.isFailure(otherView)).toBe(true);
|
|
324
|
+
if (Result.isFailure(otherView)) {
|
|
325
|
+
expect(otherView.failure._tag).toBe("NotFoundError");
|
|
326
|
+
}
|
|
327
|
+
}).pipe(Effect.provide(securityTestLayer)));
|
|
328
|
+
it.effect("request-query-bound rule — cache must not leak a record across different query values", () => Effect.gen(function* () {
|
|
329
|
+
yield* setupTenantDocsTable;
|
|
330
|
+
const svc = yield* CollectionService;
|
|
331
|
+
const created = yield* svc.create(TenantDocs, { tenantId: "tenant-1", body: "secret" }, noCtx);
|
|
332
|
+
const id = created["id"];
|
|
333
|
+
const ctxTenant1 = { headers: {}, query: { tenantId: "tenant-1" } };
|
|
334
|
+
const ctxTenant2 = { headers: {}, query: { tenantId: "tenant-2" } };
|
|
335
|
+
// tenant-1 views the doc — populates the cache
|
|
336
|
+
const tenant1View = yield* svc.view(TenantDocs, id, ctxTenant1);
|
|
337
|
+
expect(tenant1View["body"]).toBe("secret");
|
|
338
|
+
// tenant-2 requests the same id/select/expand within the TTL window.
|
|
339
|
+
// The rule (tenantId == @request_query_tenantId) denies tenant-2 at the DB layer —
|
|
340
|
+
// a cache hit must not bypass that check.
|
|
341
|
+
const tenant2View = yield* Effect.result(svc.view(TenantDocs, id, ctxTenant2));
|
|
342
|
+
expect(Result.isFailure(tenant2View)).toBe(true);
|
|
343
|
+
if (Result.isFailure(tenant2View)) {
|
|
344
|
+
expect(tenant2View.failure._tag).toBe("NotFoundError");
|
|
345
|
+
}
|
|
346
|
+
}).pipe(Effect.provide(securityTestLayer)));
|
|
347
|
+
it.effect("ctx.admin-bound field visibility — cache must not leak hidden fields to non-admin requests", () => Effect.gen(function* () {
|
|
348
|
+
yield* setupAccountsTable;
|
|
349
|
+
const svc = yield* CollectionService;
|
|
350
|
+
const created = yield* svc.create(Accounts, { email: "a@b.com", password: "hashed-secret" }, adminCtx);
|
|
351
|
+
const id = created["id"];
|
|
352
|
+
// Admin views the record — password is x-hidden but admin bypasses field hiding —
|
|
353
|
+
// populates the cache with the password included. The password is hashed on
|
|
354
|
+
// write, so assert presence rather than the literal seeded value.
|
|
355
|
+
const adminView = yield* svc.view(Accounts, id, adminCtx);
|
|
356
|
+
expect(typeof adminView["password"]).toBe("string");
|
|
357
|
+
// Non-admin requests the same id/select/expand within the TTL window.
|
|
358
|
+
// A cache hit must not hand back the admin's unfiltered response.
|
|
359
|
+
const nonAdminView = yield* svc.view(Accounts, id, noCtx);
|
|
360
|
+
expect(nonAdminView["password"]).toBeUndefined();
|
|
361
|
+
}).pipe(Effect.provide(securityTestLayer)));
|
|
362
|
+
});
|
|
203
363
|
describe("Performance — cache vs. DB", () => {
|
|
204
364
|
// Each test uses a fresh :memory: DB and empty cache via perfTestLayer.
|
|
205
365
|
// slow_posts VIEW forces 100 000 recursive CTE steps + full SUM aggregation
|