@cosmicdrift/kumiko-framework 1.0.0 → 2.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/README.md +9 -38
- package/package.json +21 -2
- package/src/__tests__/anonymous-access.integration.test.ts +185 -0
- package/src/__tests__/consumer-cli.integration.test.ts +172 -0
- package/src/__tests__/entity-permalink-open.integration.test.ts +94 -0
- package/src/__tests__/full-stack.integration.test.ts +1 -1
- package/src/__tests__/schema-cli-temporal-polyfill.test.ts +48 -0
- package/src/__tests__/schema-cli.integration.test.ts +50 -0
- package/src/__tests__/store-table.integration.test.ts +93 -0
- package/src/api/__tests__/api.test.ts +373 -0
- package/src/api/__tests__/auth-middleware-anonymous-access-boot.test.ts +40 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +17 -1
- package/src/api/__tests__/auth-routes-invalid-body-invite.test.ts +253 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-confirm.test.ts +222 -0
- package/src/api/__tests__/auth-routes-mfa-preauth-enable-start.test.ts +249 -0
- package/src/api/__tests__/auth-routes-mfa-verify.test.ts +202 -0
- package/src/api/__tests__/auth-routes-trusted-proxy.test.ts +135 -0
- package/src/api/__tests__/batch.integration.test.ts +101 -11
- package/src/api/__tests__/csrf-constants-sync.test.ts +20 -0
- package/src/api/__tests__/dispatcher-live.integration.test.ts +74 -0
- package/src/api/__tests__/jwt.test.ts +200 -1
- package/src/api/__tests__/login-rate-limiter-sweep.test.ts +50 -0
- package/src/api/__tests__/pat-scope.test.ts +36 -0
- package/src/api/__tests__/pii-leak-guard.integration.test.ts +103 -0
- package/src/api/__tests__/redis-login-rate-limiter.integration.test.ts +138 -0
- package/src/api/__tests__/request-id-middleware.test.ts +51 -0
- package/src/api/__tests__/server-boot-guards.test.ts +71 -0
- package/src/api/__tests__/server-jwt-ttl.test.ts +58 -0
- package/src/api/__tests__/sse-broker.test.ts +57 -0
- package/src/api/__tests__/sse-route.test.ts +4 -0
- package/src/api/api-constants.ts +11 -0
- package/src/api/auth-middleware.ts +248 -40
- package/src/api/auth-routes.ts +576 -95
- package/src/api/index.ts +13 -4
- package/src/api/jwt.ts +170 -11
- package/src/api/pat-scope.ts +14 -0
- package/src/api/pii-leak-guard.ts +47 -0
- package/src/api/request-context.ts +3 -0
- package/src/api/request-id-middleware.ts +2 -0
- package/src/api/routes.ts +169 -2
- package/src/api/server.ts +112 -16
- package/src/api/sse-broker.ts +39 -0
- package/src/bun-db/__tests__/PATTERN.md +0 -1
- package/src/bun-db/__tests__/coerce-row-temporal.test.ts +41 -0
- package/src/bun-db/__tests__/select-many-retry.test.ts +79 -0
- package/src/bun-db/__tests__/write-brand.test.ts +21 -0
- package/src/bun-db/connection.ts +3 -3
- package/src/bun-db/index.ts +2 -0
- package/src/bun-db/query.ts +105 -32
- package/src/consumer-cli.ts +134 -0
- package/src/crypto/__tests__/blind-index.test.ts +130 -0
- package/src/crypto/__tests__/event-pii.test.ts +161 -0
- package/src/crypto/__tests__/kek-rotation.integration.test.ts +180 -0
- package/src/crypto/__tests__/kms-adapter-contract.ts +134 -0
- package/src/crypto/__tests__/kms-adapter.contract.test.ts +4 -0
- package/src/crypto/__tests__/pg-kms-adapter.integration.test.ts +117 -0
- package/src/crypto/__tests__/pii-field-encryption.test.ts +376 -0
- package/src/crypto/__tests__/request-kms-cache.test.ts +74 -0
- package/src/crypto/__tests__/subject-resolver.test.ts +108 -0
- package/src/crypto/blind-index.ts +118 -0
- package/src/crypto/event-pii.ts +70 -0
- package/src/crypto/in-memory-kms-adapter.ts +50 -0
- package/src/crypto/index.ts +67 -0
- package/src/crypto/kms-adapter.ts +2 -0
- package/src/crypto/pg-kms-adapter.ts +295 -0
- package/src/crypto/pii-field-encryption.ts +248 -0
- package/src/crypto/request-kms-cache.ts +38 -0
- package/src/crypto/subject-resolver.ts +91 -0
- package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +191 -0
- package/src/db/__tests__/blind-index.integration.test.ts +248 -0
- package/src/db/__tests__/build-filter-where.test.ts +34 -0
- package/src/db/__tests__/collect-table-metas.test.ts +10 -10
- package/src/db/__tests__/config-seed.integration.test.ts +13 -5
- package/src/db/__tests__/dialect-instant.test.ts +1 -4
- package/src/db/__tests__/entity-field-encryption.test.ts +7 -7
- package/src/db/__tests__/entity-table-meta-source.test.ts +50 -0
- package/src/db/__tests__/event-store-executor-context.pii-roundtrip.test.ts +67 -0
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +402 -0
- package/src/db/__tests__/event-store-executor.integration.test.ts +299 -29
- package/src/db/__tests__/feature-table-sources.test.ts +34 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +118 -39
- package/src/db/__tests__/instant-to-driver-temporal.test.ts +21 -0
- package/src/db/__tests__/located-timestamp.test.ts +19 -0
- package/src/db/__tests__/migrate-generator.test.ts +21 -0
- package/src/db/__tests__/migrate-runner.test.ts +61 -0
- package/src/db/__tests__/number-field-fractional.integration.test.ts +58 -0
- package/src/db/__tests__/rebuild-marker.test.ts +13 -3
- package/src/db/__tests__/replay-migration-sql.test.ts +384 -0
- package/src/db/__tests__/schema-inspection.test.ts +7 -0
- package/src/db/__tests__/schema-migration.integration.test.ts +1 -1
- package/src/db/__tests__/table-builder-meta-lockstep.test.ts +39 -0
- package/src/db/__tests__/tenant-db-where-merge.test.ts +49 -3
- package/src/db/__tests__/tenant-db.integration.test.ts +6 -2
- package/src/db/api.ts +2 -2
- package/src/db/apply-entity-event.ts +18 -14
- package/src/db/blind-index-cleanup.ts +55 -0
- package/src/db/bun-provider.ts +2 -2
- package/src/db/collect-table-metas.ts +6 -7
- package/src/db/config-seed.ts +9 -9
- package/src/db/connection.ts +7 -12
- package/src/db/cursor.ts +1 -18
- package/src/db/dialect.ts +20 -26
- package/src/db/entity-field-encryption.ts +81 -24
- package/src/db/entity-table-meta-types.ts +2 -0
- package/src/db/entity-table-meta.ts +63 -90
- package/src/db/event-store-executor-context.ts +404 -0
- package/src/db/event-store-executor-read.ts +275 -0
- package/src/db/event-store-executor-write.ts +644 -0
- package/src/db/event-store-executor.ts +28 -1155
- package/src/db/feature-table-sources.ts +6 -5
- package/src/db/index.ts +20 -3
- package/src/db/located-timestamp.ts +4 -0
- package/src/db/migrate-generator.ts +39 -6
- package/src/db/migrate-runner.ts +107 -11
- package/src/db/pg-error.ts +9 -1
- package/src/db/postgres-provider.ts +2 -2
- package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +80 -0
- package/src/db/queries/backfill-pii.ts +277 -0
- package/src/db/queries/ddl.ts +45 -0
- package/src/db/queries/event-consumer.ts +35 -2
- package/src/db/queries/event-store.ts +126 -19
- package/src/db/queries/projection-rebuild.ts +22 -8
- package/src/db/queries/shadow-swap.ts +183 -0
- package/src/db/queries/test-stack.ts +4 -30
- package/src/db/query-api.ts +1 -0
- package/src/db/query.ts +1 -0
- package/src/db/rebuild-marker.ts +18 -2
- package/src/db/reference-data.ts +2 -3
- package/src/db/replay-migration-sql.ts +257 -0
- package/src/db/schema-inspection.ts +1 -1
- package/src/db/table-builder.ts +102 -71
- package/src/db/tenant-db.ts +15 -53
- package/src/engine/__tests__/boot-validator-action-wiring.test.ts +242 -0
- package/src/engine/__tests__/boot-validator-boot-check.test.ts +99 -0
- package/src/engine/__tests__/boot-validator-dashboard.test.ts +237 -0
- package/src/engine/__tests__/boot-validator-entity-list.test.ts +104 -0
- package/src/engine/__tests__/boot-validator-gdpr-storage.test.ts +7 -69
- package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +26 -5
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +3 -19
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +246 -4
- package/src/engine/__tests__/boot-validator.test.ts +219 -15
- package/src/engine/__tests__/build-app-schema.test.ts +82 -0
- package/src/engine/__tests__/build-config-feature-schema.test.ts +125 -0
- package/src/engine/__tests__/build-target.test.ts +3 -11
- package/src/engine/__tests__/codemod-pipeline.test.ts +152 -21
- package/src/engine/__tests__/config-helpers.test.ts +16 -0
- package/src/engine/__tests__/define-feature-entity-mapping.test.ts +6 -0
- package/src/engine/__tests__/define-roles.test.ts +21 -0
- package/src/engine/__tests__/engine.test.ts +163 -1
- package/src/engine/__tests__/entity-handlers.test.ts +80 -0
- package/src/engine/__tests__/event-migration-declarative.test.ts +65 -0
- package/src/engine/__tests__/event-type-map-augmentation.test.ts +24 -0
- package/src/engine/__tests__/extend-entity-projection.test.ts +123 -0
- package/src/engine/__tests__/factories-time.test.ts +2 -66
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +28 -0
- package/src/engine/__tests__/feature-manifest.test.ts +31 -1
- package/src/engine/__tests__/field-access.test.ts +23 -1
- package/src/engine/__tests__/hook-phases.test.ts +5 -5
- package/src/engine/__tests__/membership-roles.test.ts +4 -10
- package/src/engine/__tests__/nav.test.ts +86 -1
- package/src/engine/__tests__/pipeline-engine.test.ts +9 -9
- package/src/engine/__tests__/pipeline-handler.integration.test.ts +18 -18
- package/src/engine/__tests__/pipeline-observability.integration.test.ts +2 -2
- package/src/engine/__tests__/pipeline-performance.integration.test.ts +3 -3
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +9 -9
- package/src/engine/__tests__/post-query-hook.test.ts +7 -7
- package/src/engine/__tests__/registrar-object-form.test.ts +141 -0
- package/src/engine/__tests__/registry.test.ts +144 -0
- package/src/engine/__tests__/schema-builder.test.ts +18 -0
- package/src/engine/__tests__/screen.test.ts +147 -1
- package/src/engine/__tests__/soft-delete-cleanup.test.ts +3 -0
- package/src/engine/__tests__/store-table.test.ts +229 -0
- package/src/engine/__tests__/tier-resolver-extension.test.ts +19 -1
- package/src/engine/__tests__/{visual-tree-patterns.test.ts → tree-actions-patterns.test.ts} +7 -95
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +15 -15
- package/src/engine/boot-validator/__tests__/config-deps.test.ts +92 -0
- package/src/engine/boot-validator/action-wiring.ts +149 -0
- package/src/engine/boot-validator/boot-check.ts +21 -0
- package/src/engine/boot-validator/config-deps.ts +40 -4
- package/src/engine/boot-validator/entity-handler.ts +20 -21
- package/src/engine/boot-validator/entity-list-screens.ts +88 -0
- package/src/engine/boot-validator/gdpr-storage.ts +0 -20
- package/src/engine/boot-validator/i18n-keys.ts +58 -4
- package/src/engine/boot-validator/index.ts +33 -12
- package/src/engine/boot-validator/nav.ts +125 -0
- package/src/engine/boot-validator/pii-retention.ts +95 -10
- package/src/engine/boot-validator/{screens-nav.ts → screens.ts} +214 -191
- package/src/engine/boot-validator/workspaces.ts +68 -0
- package/src/engine/build-app-schema.ts +16 -0
- package/src/engine/build-config-feature-schema.ts +44 -7
- package/src/engine/codemod/pipeline-codemod.ts +5 -5
- package/src/engine/config-helpers.ts +15 -0
- package/src/engine/constants.ts +32 -6
- package/src/engine/create-app.ts +11 -0
- package/src/engine/define-feature.ts +95 -947
- package/src/engine/define-handler.ts +28 -94
- package/src/engine/define-workflow.ts +1 -1
- package/src/engine/effective-features.ts +12 -2
- package/src/engine/entity-handlers.ts +76 -11
- package/src/engine/extensions/tenant-data.ts +19 -0
- package/src/engine/extensions/user-data.ts +29 -2
- package/src/engine/factories.ts +8 -49
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +26 -29
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/feature.ts +9 -0
- package/src/engine/feature-ast/__tests__/fixtures/cross-file-registrar/screens.ts +4 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +1 -2
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +18 -8
- package/src/engine/feature-ast/__tests__/parse.test.ts +1640 -160
- package/src/engine/feature-ast/__tests__/patch.test.ts +206 -12
- package/src/engine/feature-ast/__tests__/patcher.test.ts +20 -23
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +380 -5
- package/src/engine/feature-ast/extractors/events.ts +322 -0
- package/src/engine/feature-ast/extractors/handlers.ts +234 -0
- package/src/engine/feature-ast/extractors/hooks.ts +243 -0
- package/src/engine/feature-ast/extractors/index.ts +34 -31
- package/src/engine/feature-ast/extractors/jobs-routes.ts +221 -0
- package/src/engine/feature-ast/extractors/projections-screens.ts +269 -0
- package/src/engine/feature-ast/extractors/round1.ts +3 -3
- package/src/engine/feature-ast/extractors/round5.ts +3 -3
- package/src/engine/feature-ast/extractors/round6.ts +2 -36
- package/src/engine/feature-ast/extractors/shared.ts +64 -6
- package/src/engine/feature-ast/index.ts +2 -4
- package/src/engine/feature-ast/parse.ts +130 -23
- package/src/engine/feature-ast/patch.ts +39 -50
- package/src/engine/feature-ast/patcher.ts +37 -38
- package/src/engine/feature-ast/patterns.ts +50 -62
- package/src/engine/feature-ast/render.ts +67 -44
- package/src/engine/feature-builder-state.ts +168 -0
- package/src/engine/feature-config-events-jobs.ts +403 -0
- package/src/engine/feature-entity-handlers.ts +208 -0
- package/src/engine/feature-manifest.ts +2 -1
- package/src/engine/feature-ui-extensions.ts +500 -0
- package/src/engine/field-access.ts +13 -2
- package/src/engine/field-helpers.ts +31 -0
- package/src/engine/handler-helpers.ts +26 -0
- package/src/engine/hook-helpers.ts +16 -0
- package/src/engine/index.ts +23 -7
- package/src/engine/membership-roles.ts +13 -0
- package/src/engine/object-form.ts +27 -0
- package/src/engine/ownership.ts +26 -79
- package/src/engine/pattern-library/__tests__/library.test.ts +7 -20
- package/src/engine/pattern-library/library.ts +44 -1152
- package/src/engine/pattern-library/mixed-schemas.ts +450 -0
- package/src/engine/pattern-library/opaque-schemas.ts +124 -0
- package/src/engine/pattern-library/shared-fields.ts +75 -0
- package/src/engine/pattern-library/static-schemas.ts +456 -0
- package/src/engine/pipeline.ts +6 -11
- package/src/engine/registry-facade.ts +362 -0
- package/src/engine/registry-ingest.ts +478 -0
- package/src/engine/registry-state.ts +388 -0
- package/src/engine/registry-validate.ts +642 -0
- package/src/engine/registry.ts +78 -1658
- package/src/engine/run-pipeline.ts +1 -1
- package/src/engine/schema-builder.ts +1 -0
- package/src/engine/screen-helpers.ts +54 -0
- package/src/engine/soft-delete-cleanup.ts +6 -2
- package/src/engine/steps/__tests__/duration-utils.test.ts +20 -0
- package/src/engine/steps/_duration-utils.ts +2 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +1 -4
- package/src/engine/tier-resolver-extension.ts +3 -2
- package/src/engine/types/config.ts +2 -482
- package/src/engine/types/define-handler.ts +2 -0
- package/src/engine/types/entity-handlers.ts +2 -0
- package/src/engine/types/event-type-map.ts +1 -37
- package/src/engine/types/feature.ts +2 -972
- package/src/engine/types/fields.ts +2 -675
- package/src/engine/types/handlers.ts +2 -774
- package/src/engine/types/hooks.ts +2 -184
- package/src/engine/types/http-route.ts +1 -54
- package/src/engine/types/identifiers.ts +1 -47
- package/src/engine/types/index.ts +86 -40
- package/src/engine/types/nav.ts +2 -63
- package/src/engine/types/ownership.ts +2 -0
- package/src/engine/types/projection.ts +2 -138
- package/src/engine/types/relations.ts +1 -51
- package/src/engine/types/screen.ts +2 -574
- package/src/engine/types/step.ts +2 -334
- package/src/engine/types/target-ref.ts +1 -21
- package/src/engine/types/tree-node.ts +1 -132
- package/src/engine/types/workspace.ts +2 -49
- package/src/engine/validate-projection-allowlist.ts +6 -6
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +120 -1
- package/src/entrypoint/index.ts +49 -6
- package/src/errors/classes.ts +22 -1
- package/src/errors/field-issue.ts +0 -3
- package/src/errors/index.ts +1 -1
- package/src/errors/write-error-info.ts +10 -22
- package/src/es-ops/README.md +1 -1
- package/src/es-ops/__tests__/runner.integration.test.ts +74 -0
- package/src/es-ops/context.ts +4 -2
- package/src/es-ops/types.ts +8 -1
- package/src/event-store/__tests__/admin-api.integration.test.ts +27 -1
- package/src/event-store/__tests__/backfill-pii.integration.test.ts +279 -0
- package/src/event-store/__tests__/event-store.integration.test.ts +201 -0
- package/src/event-store/__tests__/row-to-stored-event.test.ts +2 -2
- package/src/event-store/__tests__/snapshot.integration.test.ts +86 -0
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +58 -0
- package/src/event-store/__tests__/upcaster.integration.test.ts +77 -45
- package/src/event-store/admin-api.ts +11 -4
- package/src/event-store/errors.ts +2 -35
- package/src/event-store/event-store.ts +64 -78
- package/src/event-store/events-schema.ts +11 -13
- package/src/event-store/index.ts +18 -3
- package/src/event-store/rebuild-dead-letter.ts +111 -0
- package/src/event-store/snapshot.ts +63 -40
- package/src/event-store/types.ts +2 -0
- package/src/files/__tests__/build-storage-key.test.ts +28 -0
- package/src/files/__tests__/file-ref-entity.test.ts +8 -0
- package/src/files/__tests__/files.integration.test.ts +24 -0
- package/src/files/__tests__/in-memory-provider.contract.test.ts +4 -0
- package/src/files/__tests__/local-provider.test.ts +31 -0
- package/src/files/__tests__/provider-resolver.test.ts +70 -0
- package/src/files/__tests__/write-stream.test.ts +13 -0
- package/src/files/file-handle.ts +2 -19
- package/src/files/file-ref-entity.ts +2 -2
- package/src/files/file-routes.ts +13 -0
- package/src/files/index.ts +1 -1
- package/src/files/local-provider.ts +27 -8
- package/src/files/provider-resolver.ts +31 -20
- package/src/files/types.ts +13 -55
- package/src/i18n/__tests__/required-surface-keys.test.ts +17 -0
- package/src/i18n/required-surface-keys.ts +120 -1
- package/src/jobs/__tests__/job-queue-depth.integration.test.ts +82 -0
- package/src/jobs/__tests__/jobs.integration.test.ts +329 -3
- package/src/jobs/job-runner.ts +82 -12
- package/src/logging/types.ts +1 -7
- package/src/migrations/pending-rebuilds.ts +1 -1
- package/src/observability/__tests__/observability.integration.test.ts +4 -1
- package/src/observability/__tests__/recording-tracer.test.ts +6 -4
- package/src/observability/index.ts +2 -0
- package/src/observability/noop-provider.ts +0 -9
- package/src/observability/recording-tracer.ts +0 -12
- package/src/observability/standard-metrics.ts +64 -2
- package/src/observability/types/index.ts +1 -29
- package/src/observability/types/metric.ts +1 -56
- package/src/observability/types/provider.ts +1 -32
- package/src/observability/types/span.ts +1 -64
- package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +2 -2
- package/src/pipeline/__tests__/dispatcher.test.ts +366 -1
- package/src/pipeline/__tests__/event-consumer-state.integration.test.ts +31 -0
- package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +126 -0
- package/src/pipeline/__tests__/event-dispatcher-rearm.integration.test.ts +263 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.test.ts +10 -0
- package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +106 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +308 -77
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +16 -8
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +80 -2
- package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +274 -0
- package/src/pipeline/__tests__/try-append-event.integration.test.ts +166 -0
- package/src/pipeline/dispatch-batch.ts +187 -0
- package/src/pipeline/dispatch-query.ts +165 -0
- package/src/pipeline/dispatch-shared.ts +826 -0
- package/src/pipeline/dispatch-stream.ts +90 -0
- package/src/pipeline/dispatch-write.ts +451 -0
- package/src/pipeline/dispatcher-utils.ts +1 -1
- package/src/pipeline/dispatcher.ts +44 -1372
- package/src/pipeline/entity-cache.ts +2 -33
- package/src/pipeline/event-consumer-state.ts +30 -2
- package/src/pipeline/event-dispatcher-admin.ts +293 -0
- package/src/pipeline/event-dispatcher-delivery.ts +305 -0
- package/src/pipeline/event-dispatcher.ts +85 -542
- package/src/pipeline/index.ts +2 -0
- package/src/pipeline/msp-rebuild.ts +42 -3
- package/src/pipeline/multi-stream-apply-context.ts +4 -42
- package/src/pipeline/projection-rebuild.ts +105 -3
- package/src/pipeline/system-hooks.ts +144 -1
- package/src/rate-limit/__tests__/resolver.integration.test.ts +18 -0
- package/src/rate-limit/resolver.ts +16 -32
- package/src/schema-cli.ts +76 -16
- package/src/search/__tests__/meilisearch-adapter.integration.test.ts +207 -185
- package/src/search/__tests__/meilisearch-ids.test.ts +30 -0
- package/src/search/__tests__/reindex-entity.integration.test.ts +144 -0
- package/src/search/index.ts +6 -0
- package/src/search/meilisearch-adapter.ts +13 -12
- package/src/search/reindex-entity.ts +177 -0
- package/src/search/types.ts +1 -39
- package/src/secrets/__tests__/contains-secret.test.ts +34 -0
- package/src/secrets/__tests__/envelope-cipher.test.ts +59 -0
- package/src/secrets/__tests__/envelope.test.ts +1 -1
- package/src/secrets/dek-cache.ts +26 -5
- package/src/secrets/envelope-cipher.ts +53 -0
- package/src/secrets/envelope.ts +5 -3
- package/src/secrets/index.ts +13 -1
- package/src/secrets/stored-envelope.ts +46 -0
- package/src/secrets/types.ts +2 -162
- package/src/stack/__tests__/event-collector.test.ts +42 -0
- package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +125 -0
- package/src/stack/db.ts +2 -1
- package/src/stack/push-entity-projection-tables.ts +4 -3
- package/src/stack/redis.ts +8 -0
- package/src/stack/request-helper.ts +38 -2
- package/src/stack/table-helpers.ts +6 -4
- package/src/stack/test-stack.ts +249 -131
- package/src/testing/__tests__/late-bound.test.ts +32 -0
- package/src/testing/__tests__/wait-for.test.ts +59 -0
- package/src/testing/boot-validator-fixture.ts +121 -0
- package/src/testing/e2e-generator.ts +8 -0
- package/src/testing/file-provider-contract.ts +104 -0
- package/src/testing/handler-context.ts +3 -1
- package/src/testing/index.ts +5 -0
- package/src/testing/late-bound.ts +5 -3
- package/src/testing/mutable-master-key-provider.ts +29 -3
- package/src/testing/wait-for.ts +3 -0
- package/src/testing/without-ambient-temporal.ts +14 -0
- package/src/time/__tests__/tz-dateline.test.ts +8 -10
- package/src/time/geo-tz.ts +1 -32
- package/src/time/index.ts +1 -0
- package/src/time/legacy-date.ts +18 -0
- package/src/time/polyfill.ts +21 -38
- package/src/time/tz-context.ts +44 -85
- package/src/ui-types/app-schema.ts +12 -0
- package/src/ui-types/index.ts +20 -7
- package/src/utils/__tests__/safe-json-temporal.test.ts +18 -0
- package/src/utils/safe-json.ts +13 -1
- package/src/__tests__/raw-table.integration.test.ts +0 -116
- package/src/bun-db/__tests__/bun-test-stack.ts +0 -6
- package/src/db/__tests__/encryption.test.ts +0 -39
- package/src/db/encryption.ts +0 -39
- package/src/db/row-helpers.ts +0 -4
- package/src/engine/__tests__/raw-table.test.ts +0 -150
- package/src/engine/__tests__/unmanaged-table.test.ts +0 -127
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +0 -184
- package/src/engine/feature-ast/extractors/round4.ts +0 -1366
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// The five write-verbs (create/update/delete/forget/restore) have several
|
|
2
|
+
// error paths the existing suites (event-store-executor.integration.test.ts,
|
|
3
|
+
// unique-violation-mapping.integration.test.ts) don't touch: entity- and
|
|
4
|
+
// field-level ownership denials, explicit version conflicts, the forget()
|
|
5
|
+
// verb entirely, and restore()'s two precondition failures.
|
|
6
|
+
|
|
7
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
8
|
+
import { asRawClient } from "../../db/query";
|
|
9
|
+
import { createEntity, createTextField } from "../../engine";
|
|
10
|
+
import { from } from "../../engine/ownership";
|
|
11
|
+
import { createEventsTable } from "../../event-store";
|
|
12
|
+
import type { EntityCache } from "../../pipeline/entity-cache";
|
|
13
|
+
import { createTestDb, type TestDb, TestUsers, unsafeCreateEntityTable } from "../../stack";
|
|
14
|
+
import { createEventStoreExecutor } from "../event-store-executor";
|
|
15
|
+
import { buildEntityTable } from "../table-builder";
|
|
16
|
+
import { createTenantDb, type TenantDb } from "../tenant-db";
|
|
17
|
+
|
|
18
|
+
let testDb: TestDb;
|
|
19
|
+
let tdb: TenantDb;
|
|
20
|
+
const admin = TestUsers.admin;
|
|
21
|
+
const nonAdmin = TestUsers.user;
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
testDb = await createTestDb();
|
|
25
|
+
await createEventsTable(testDb.db);
|
|
26
|
+
tdb = createTenantDb(testDb.db, admin.tenantId);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterAll(async () => {
|
|
30
|
+
await testDb.cleanup();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// entity-level ownership_denied — create/update/delete/forget/restore
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
const restrictedEntity = createEntity({
|
|
38
|
+
table: "read_es_write_restricted",
|
|
39
|
+
fields: {
|
|
40
|
+
email: createTextField({ required: true }),
|
|
41
|
+
},
|
|
42
|
+
softDelete: true,
|
|
43
|
+
access: { write: { Admin: "all" } },
|
|
44
|
+
});
|
|
45
|
+
const restrictedTable = buildEntityTable("esWriteRestricted", restrictedEntity);
|
|
46
|
+
|
|
47
|
+
describe("event-store-executor write-verbs — entity-level ownership_denied", () => {
|
|
48
|
+
const crud = createEventStoreExecutor(restrictedTable, restrictedEntity, {
|
|
49
|
+
entityName: "esWriteRestricted",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
beforeAll(async () => {
|
|
53
|
+
await unsafeCreateEntityTable(testDb.db, restrictedEntity, "esWriteRestricted");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
beforeEach(async () => {
|
|
57
|
+
await asRawClient(testDb.db).unsafe(
|
|
58
|
+
`TRUNCATE kumiko_events, read_es_write_restricted RESTART IDENTITY CASCADE`,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("create: role without a write-rule → ownership_denied", async () => {
|
|
63
|
+
const result = await crud.create({ email: "denied@test.de" }, nonAdmin, tdb);
|
|
64
|
+
expect(result.isSuccess).toBe(false);
|
|
65
|
+
if (result.isSuccess) return;
|
|
66
|
+
expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("update: role without a write-rule → ownership_denied", async () => {
|
|
70
|
+
const created = await crud.create({ email: "owner@test.de" }, admin, tdb);
|
|
71
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
72
|
+
|
|
73
|
+
const result = await crud.update(
|
|
74
|
+
{ id: created.data.id, version: 1, changes: { email: "changed@test.de" } },
|
|
75
|
+
nonAdmin,
|
|
76
|
+
tdb,
|
|
77
|
+
);
|
|
78
|
+
expect(result.isSuccess).toBe(false);
|
|
79
|
+
if (result.isSuccess) return;
|
|
80
|
+
expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("delete: role without a write-rule → ownership_denied", async () => {
|
|
84
|
+
const created = await crud.create({ email: "todelete@test.de" }, admin, tdb);
|
|
85
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
86
|
+
|
|
87
|
+
const result = await crud.delete({ id: created.data.id }, nonAdmin, tdb);
|
|
88
|
+
expect(result.isSuccess).toBe(false);
|
|
89
|
+
if (result.isSuccess) return;
|
|
90
|
+
expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("forget: role without a write-rule → ownership_denied", async () => {
|
|
94
|
+
const created = await crud.create({ email: "toforget@test.de" }, admin, tdb);
|
|
95
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
96
|
+
|
|
97
|
+
const result = await crud.forget({ id: created.data.id }, nonAdmin, tdb);
|
|
98
|
+
expect(result.isSuccess).toBe(false);
|
|
99
|
+
if (result.isSuccess) return;
|
|
100
|
+
expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("forget: happy path hard-deletes the row and appends a forgotten event", async () => {
|
|
104
|
+
const created = await crud.create({ email: "gone@test.de" }, admin, tdb);
|
|
105
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
106
|
+
|
|
107
|
+
const result = await crud.forget({ id: created.data.id }, admin, tdb);
|
|
108
|
+
expect(result.isSuccess).toBe(true);
|
|
109
|
+
|
|
110
|
+
const rows = (await asRawClient(testDb.db).unsafe(
|
|
111
|
+
`SELECT id FROM read_es_write_restricted WHERE email = 'gone@test.de'`,
|
|
112
|
+
)) as unknown[];
|
|
113
|
+
expect(rows).toHaveLength(0);
|
|
114
|
+
|
|
115
|
+
const events = (await asRawClient(testDb.db).unsafe(
|
|
116
|
+
`SELECT type FROM kumiko_events WHERE type = 'esWriteRestricted.forgotten'`,
|
|
117
|
+
)) as unknown[];
|
|
118
|
+
expect(events).toHaveLength(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("restore: role without a write-rule → ownership_denied", async () => {
|
|
122
|
+
const created = await crud.create({ email: "torestore@test.de" }, admin, tdb);
|
|
123
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
124
|
+
const deleted = await crud.delete({ id: created.data.id }, admin, tdb);
|
|
125
|
+
if (!deleted.isSuccess) throw new Error("setup failed: delete");
|
|
126
|
+
|
|
127
|
+
const result = await crud.restore({ id: created.data.id }, nonAdmin, tdb);
|
|
128
|
+
expect(result.isSuccess).toBe(false);
|
|
129
|
+
if (result.isSuccess) return;
|
|
130
|
+
expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("restore: not yet deleted row → not_deleted", async () => {
|
|
134
|
+
const created = await crud.create({ email: "notdeleted@test.de" }, admin, tdb);
|
|
135
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
136
|
+
|
|
137
|
+
const result = await crud.restore({ id: created.data.id }, admin, tdb);
|
|
138
|
+
expect(result.isSuccess).toBe(false);
|
|
139
|
+
if (result.isSuccess) return;
|
|
140
|
+
expect((result.error.details as { reason?: string }).reason).toBe("not_deleted");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// =============================================================================
|
|
145
|
+
// restore() on a non-soft-delete entity → soft_delete_not_enabled
|
|
146
|
+
// =============================================================================
|
|
147
|
+
|
|
148
|
+
const hardDeleteEntity = createEntity({
|
|
149
|
+
table: "read_es_write_hard",
|
|
150
|
+
fields: {
|
|
151
|
+
email: createTextField({ required: true }),
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const hardDeleteTable = buildEntityTable("esWriteHard", hardDeleteEntity);
|
|
155
|
+
|
|
156
|
+
describe("event-store-executor write-verbs — restore without softDelete", () => {
|
|
157
|
+
const crud = createEventStoreExecutor(hardDeleteTable, hardDeleteEntity, {
|
|
158
|
+
entityName: "esWriteHard",
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
beforeAll(async () => {
|
|
162
|
+
await unsafeCreateEntityTable(testDb.db, hardDeleteEntity, "esWriteHard");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
beforeEach(async () => {
|
|
166
|
+
await asRawClient(testDb.db).unsafe(
|
|
167
|
+
`TRUNCATE kumiko_events, read_es_write_hard RESTART IDENTITY CASCADE`,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("restore on an entity without softDelete → soft_delete_not_enabled", async () => {
|
|
172
|
+
const created = await crud.create({ email: "hard@test.de" }, admin, tdb);
|
|
173
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
174
|
+
|
|
175
|
+
const result = await crud.restore({ id: created.data.id }, admin, tdb);
|
|
176
|
+
expect(result.isSuccess).toBe(false);
|
|
177
|
+
if (result.isSuccess) return;
|
|
178
|
+
expect((result.error.details as { reason?: string }).reason).toBe("soft_delete_not_enabled");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// =============================================================================
|
|
183
|
+
// field-level ownership_denied — checkWriteFieldOwnership via a row-scoped rule
|
|
184
|
+
// =============================================================================
|
|
185
|
+
|
|
186
|
+
const ownedFieldEntity = createEntity({
|
|
187
|
+
table: "read_es_write_owned_field",
|
|
188
|
+
fields: {
|
|
189
|
+
authorId: createTextField({ required: true }),
|
|
190
|
+
note: createTextField({
|
|
191
|
+
access: { write: { Admin: "all", User: from("user:id", "authorId") } },
|
|
192
|
+
}),
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
const ownedFieldTable = buildEntityTable("esWriteOwnedField", ownedFieldEntity);
|
|
196
|
+
|
|
197
|
+
describe("event-store-executor write-verbs — field-level ownership_denied", () => {
|
|
198
|
+
const crud = createEventStoreExecutor(ownedFieldTable, ownedFieldEntity, {
|
|
199
|
+
entityName: "esWriteOwnedField",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
beforeAll(async () => {
|
|
203
|
+
await unsafeCreateEntityTable(testDb.db, ownedFieldEntity, "esWriteOwnedField");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
beforeEach(async () => {
|
|
207
|
+
await asRawClient(testDb.db).unsafe(
|
|
208
|
+
`TRUNCATE kumiko_events, read_es_write_owned_field RESTART IDENTITY CASCADE`,
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("create: writing `note` while authorId names someone else → ownership_denied (field scope)", async () => {
|
|
213
|
+
const result = await crud.create(
|
|
214
|
+
{ authorId: TestUsers.driver.id, note: "not mine" },
|
|
215
|
+
nonAdmin,
|
|
216
|
+
tdb,
|
|
217
|
+
);
|
|
218
|
+
expect(result.isSuccess).toBe(false);
|
|
219
|
+
if (result.isSuccess) return;
|
|
220
|
+
const details = result.error.details as { reason?: string; scope?: string; field?: string };
|
|
221
|
+
expect(details.reason).toBe("ownership_denied");
|
|
222
|
+
expect(details.scope).toBe("field");
|
|
223
|
+
expect(details.field).toBe("note");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("create: writing `note` as the named author succeeds", async () => {
|
|
227
|
+
const result = await crud.create({ authorId: nonAdmin.id, note: "mine" }, nonAdmin, tdb);
|
|
228
|
+
expect(result.isSuccess).toBe(true);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("update: changing `note` on someone else's row → ownership_denied (field scope)", async () => {
|
|
232
|
+
const created = await crud.create(
|
|
233
|
+
{ authorId: TestUsers.driver.id, note: "original" },
|
|
234
|
+
admin,
|
|
235
|
+
tdb,
|
|
236
|
+
);
|
|
237
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
238
|
+
|
|
239
|
+
const result = await crud.update(
|
|
240
|
+
{ id: created.data.id, version: 1, changes: { note: "hijacked" } },
|
|
241
|
+
nonAdmin,
|
|
242
|
+
tdb,
|
|
243
|
+
);
|
|
244
|
+
expect(result.isSuccess).toBe(false);
|
|
245
|
+
if (result.isSuccess) return;
|
|
246
|
+
expect((result.error.details as { reason?: string }).reason).toBe("ownership_denied");
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// =============================================================================
|
|
251
|
+
// version_conflict — explicit-id create collision, missing version on update
|
|
252
|
+
// =============================================================================
|
|
253
|
+
|
|
254
|
+
const versionEntity = createEntity({
|
|
255
|
+
table: "read_es_write_version",
|
|
256
|
+
fields: {
|
|
257
|
+
email: createTextField({ required: true }),
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
const versionTable = buildEntityTable("esWriteVersion", versionEntity);
|
|
261
|
+
|
|
262
|
+
describe("event-store-executor write-verbs — version_conflict edge cases", () => {
|
|
263
|
+
const crud = createEventStoreExecutor(versionTable, versionEntity, {
|
|
264
|
+
entityName: "esWriteVersion",
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
beforeAll(async () => {
|
|
268
|
+
await unsafeCreateEntityTable(testDb.db, versionEntity, "esWriteVersion");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
beforeEach(async () => {
|
|
272
|
+
await asRawClient(testDb.db).unsafe(
|
|
273
|
+
`TRUNCATE kumiko_events, read_es_write_version RESTART IDENTITY CASCADE`,
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("create with an explicit id that already has a stream → version_conflict", async () => {
|
|
278
|
+
const first = await crud.create({ email: "first@test.de" }, admin, tdb);
|
|
279
|
+
if (!first.isSuccess) throw new Error("setup failed");
|
|
280
|
+
|
|
281
|
+
const collision = await crud.create({ id: first.data.id, email: "second@test.de" }, admin, tdb);
|
|
282
|
+
expect(collision.isSuccess).toBe(false);
|
|
283
|
+
if (collision.isSuccess) return;
|
|
284
|
+
expect(collision.error.code).toBe("version_conflict");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("update without a version field (and no skipOptimisticLock) → version_conflict", async () => {
|
|
288
|
+
const created = await crud.create({ email: "noversion@test.de" }, admin, tdb);
|
|
289
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
290
|
+
|
|
291
|
+
const result = await crud.update(
|
|
292
|
+
{ id: created.data.id, changes: { email: "changed@test.de" } },
|
|
293
|
+
admin,
|
|
294
|
+
tdb,
|
|
295
|
+
);
|
|
296
|
+
expect(result.isSuccess).toBe(false);
|
|
297
|
+
if (result.isSuccess) return;
|
|
298
|
+
expect(result.error.code).toBe("version_conflict");
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// =============================================================================
|
|
303
|
+
// Concurrent update race → EventStoreVersionConflict catch + entityCache.del
|
|
304
|
+
// on forget/restore (create/update/delete already exercise cache in the
|
|
305
|
+
// main suite; forget/restore del() stayed uncovered).
|
|
306
|
+
// =============================================================================
|
|
307
|
+
|
|
308
|
+
const raceEntity = createEntity({
|
|
309
|
+
table: "read_es_write_race",
|
|
310
|
+
fields: {
|
|
311
|
+
email: createTextField({ required: true }),
|
|
312
|
+
},
|
|
313
|
+
softDelete: true,
|
|
314
|
+
});
|
|
315
|
+
const raceTable = buildEntityTable("esWriteRace", raceEntity);
|
|
316
|
+
|
|
317
|
+
describe("event-store-executor write-verbs — concurrent version race + cache", () => {
|
|
318
|
+
const store = new Map<string, Record<string, unknown>>();
|
|
319
|
+
const entityCache: EntityCache = {
|
|
320
|
+
get: async (tenantId, name, id) => store.get(`${tenantId}:${name}:${id}`) ?? null,
|
|
321
|
+
mget: async () => new Map(),
|
|
322
|
+
set: async (tenantId, name, id, data) => {
|
|
323
|
+
store.set(`${tenantId}:${name}:${id}`, data);
|
|
324
|
+
},
|
|
325
|
+
mset: async (tenantId, name, entries) => {
|
|
326
|
+
for (const { id, data } of entries) store.set(`${tenantId}:${name}:${id}`, data);
|
|
327
|
+
},
|
|
328
|
+
del: async (tenantId, name, id) => {
|
|
329
|
+
store.delete(`${tenantId}:${name}:${id}`);
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
const crud = createEventStoreExecutor(raceTable, raceEntity, {
|
|
333
|
+
entityName: "esWriteRace",
|
|
334
|
+
entityCache,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
beforeAll(async () => {
|
|
338
|
+
await unsafeCreateEntityTable(testDb.db, raceEntity, "esWriteRace");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
beforeEach(async () => {
|
|
342
|
+
store.clear();
|
|
343
|
+
await asRawClient(testDb.db).unsafe(
|
|
344
|
+
`TRUNCATE kumiko_events, read_es_write_race RESTART IDENTITY CASCADE`,
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("two concurrent updates with the same version → one wins, one version_conflict", async () => {
|
|
349
|
+
const created = await crud.create({ email: "race@test.de" }, admin, tdb);
|
|
350
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
351
|
+
const id = created.data.id;
|
|
352
|
+
|
|
353
|
+
const [a, b] = await Promise.all([
|
|
354
|
+
crud.update({ id, version: 1, changes: { email: "a@test.de" } }, admin, tdb),
|
|
355
|
+
crud.update({ id, version: 1, changes: { email: "b@test.de" } }, admin, tdb),
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
const results = [a, b];
|
|
359
|
+
expect(results.filter((r) => r.isSuccess)).toHaveLength(1);
|
|
360
|
+
expect(results.filter((r) => !r.isSuccess && r.error.code === "version_conflict")).toHaveLength(
|
|
361
|
+
1,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// Guard against the known Bun.SQL pooled-connection-poisoning class: the
|
|
365
|
+
// losing update above provoked a real 23505 unique-violation on the
|
|
366
|
+
// shared pool. The two tests below this one reuse `tdb`/`testDb.db`, so
|
|
367
|
+
// a poisoned connection would surface as their unrelated queries
|
|
368
|
+
// failing — a trivial round-trip here catches that immediately instead
|
|
369
|
+
// of leaving it to whichever later test happens to hit the bad
|
|
370
|
+
// connection.
|
|
371
|
+
const healthCheck = (await asRawClient(testDb.db).unsafe(`SELECT 1 AS ok`)) as Array<{
|
|
372
|
+
ok: number;
|
|
373
|
+
}>;
|
|
374
|
+
expect(healthCheck[0]?.ok).toBe(1);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("forget with entityCache clears the cache entry", async () => {
|
|
378
|
+
const created = await crud.create({ email: "cache-forget@test.de" }, admin, tdb);
|
|
379
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
380
|
+
const id = created.data.id;
|
|
381
|
+
const key = `${admin.tenantId}:esWriteRace:${id}`;
|
|
382
|
+
store.set(key, { email: "poison" });
|
|
383
|
+
|
|
384
|
+
const result = await crud.forget({ id }, admin, tdb);
|
|
385
|
+
expect(result.isSuccess).toBe(true);
|
|
386
|
+
expect(store.has(key)).toBe(false);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("restore with entityCache clears the cache entry", async () => {
|
|
390
|
+
const created = await crud.create({ email: "cache-restore@test.de" }, admin, tdb);
|
|
391
|
+
if (!created.isSuccess) throw new Error("setup failed");
|
|
392
|
+
const id = created.data.id;
|
|
393
|
+
await crud.delete({ id }, admin, tdb);
|
|
394
|
+
|
|
395
|
+
const key = `${admin.tenantId}:esWriteRace:${id}`;
|
|
396
|
+
store.set(key, { email: "poison" });
|
|
397
|
+
|
|
398
|
+
const result = await crud.restore({ id }, admin, tdb);
|
|
399
|
+
expect(result.isSuccess).toBe(true);
|
|
400
|
+
expect(store.has(key)).toBe(false);
|
|
401
|
+
});
|
|
402
|
+
});
|