@cosmicdrift/kumiko-framework 0.1.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 +159 -0
- package/package.json +91 -0
- package/src/__tests__/anonymous-access.integration.ts +325 -0
- package/src/__tests__/error-contract.integration.ts +435 -0
- package/src/__tests__/field-access.integration.ts +269 -0
- package/src/__tests__/full-stack.integration.ts +914 -0
- package/src/__tests__/ownership.integration.ts +449 -0
- package/src/__tests__/reference-data.integration.ts +198 -0
- package/src/__tests__/transition-guard.integration.ts +340 -0
- package/src/api/__tests__/api.test.ts +337 -0
- package/src/api/__tests__/auth-middleware-transport.test.ts +80 -0
- package/src/api/__tests__/auth-routes-cookie.test.ts +179 -0
- package/src/api/__tests__/batch.integration.ts +404 -0
- package/src/api/__tests__/body-limit.test.ts +88 -0
- package/src/api/__tests__/csrf-middleware.test.ts +97 -0
- package/src/api/__tests__/dispatcher-live.integration.ts +216 -0
- package/src/api/__tests__/metrics-endpoint.test.ts +126 -0
- package/src/api/__tests__/nested-write.integration.ts +213 -0
- package/src/api/__tests__/readiness.test.ts +76 -0
- package/src/api/__tests__/request-id-middleware.test.ts +72 -0
- package/src/api/__tests__/sse-broker.test.ts +58 -0
- package/src/api/__tests__/sse-route.test.ts +112 -0
- package/src/api/anonymous-cookie.ts +60 -0
- package/src/api/api-constants.ts +64 -0
- package/src/api/auth-middleware.ts +418 -0
- package/src/api/auth-routes.ts +982 -0
- package/src/api/csrf-middleware.ts +77 -0
- package/src/api/index.ts +31 -0
- package/src/api/jwt.ts +66 -0
- package/src/api/observability-middleware.ts +89 -0
- package/src/api/readiness.ts +132 -0
- package/src/api/request-context.ts +49 -0
- package/src/api/request-id-middleware.ts +50 -0
- package/src/api/route-registrars.ts +195 -0
- package/src/api/routes.ts +135 -0
- package/src/api/server.ts +640 -0
- package/src/api/sse-broker.ts +71 -0
- package/src/api/sse-route.ts +62 -0
- package/src/api/tokens.ts +16 -0
- package/src/db/__tests__/apply-entity-event-tenant.integration.ts +159 -0
- package/src/db/__tests__/compound-types.test.ts +114 -0
- package/src/db/__tests__/connection-options.test.ts +68 -0
- package/src/db/__tests__/cursor.test.ts +41 -0
- package/src/db/__tests__/db-helpers.test.ts +369 -0
- package/src/db/__tests__/dialect-instant.test.ts +50 -0
- package/src/db/__tests__/drizzle-helpers.integration.ts +186 -0
- package/src/db/__tests__/drizzle-table-types.test.ts +162 -0
- package/src/db/__tests__/encryption.test.ts +39 -0
- package/src/db/__tests__/event-store-executor-list.integration.ts +313 -0
- package/src/db/__tests__/event-store-executor.integration.ts +235 -0
- package/src/db/__tests__/implicit-projection-equivalence.integration.ts +304 -0
- package/src/db/__tests__/located-timestamp.test.ts +184 -0
- package/src/db/__tests__/money.test.ts +199 -0
- package/src/db/__tests__/multi-row-insert.integration.ts +76 -0
- package/src/db/__tests__/parse-auto-verb.test.ts +70 -0
- package/src/db/__tests__/required-not-null-migration-safety.integration.ts +105 -0
- package/src/db/__tests__/row-helpers.test.ts +59 -0
- package/src/db/__tests__/schema-migration.integration.ts +273 -0
- package/src/db/__tests__/table-builder-indexes.test.ts +153 -0
- package/src/db/__tests__/table-builder-required.test.ts +216 -0
- package/src/db/__tests__/tenant-db.integration.ts +606 -0
- package/src/db/__tests__/unique-violation-mapping.integration.ts +166 -0
- package/src/db/apply-entity-event.ts +188 -0
- package/src/db/assert-exists-in.ts +59 -0
- package/src/db/compound-types.ts +47 -0
- package/src/db/connection.ts +104 -0
- package/src/db/cursor.ts +83 -0
- package/src/db/dialect.ts +109 -0
- package/src/db/eagerload.ts +174 -0
- package/src/db/encryption.ts +39 -0
- package/src/db/event-store-executor.ts +906 -0
- package/src/db/index.ts +55 -0
- package/src/db/located-timestamp.ts +114 -0
- package/src/db/money.ts +120 -0
- package/src/db/pg-error.ts +46 -0
- package/src/db/reference-data.ts +77 -0
- package/src/db/row-helpers.ts +53 -0
- package/src/db/schema-inspection.ts +25 -0
- package/src/db/table-builder.ts +475 -0
- package/src/db/tenant-db.ts +434 -0
- package/src/engine/__tests__/auth-claims-registrar.test.ts +74 -0
- package/src/engine/__tests__/boot-validator-located-timestamps.test.ts +108 -0
- package/src/engine/__tests__/boot-validator.test.ts +1865 -0
- package/src/engine/__tests__/build-app-schema.test.ts +154 -0
- package/src/engine/__tests__/claim-keys.test.ts +274 -0
- package/src/engine/__tests__/config-helpers.test.ts +236 -0
- package/src/engine/__tests__/effective-features.test.ts +86 -0
- package/src/engine/__tests__/engine.test.ts +1461 -0
- package/src/engine/__tests__/entity-handlers.test.ts +274 -0
- package/src/engine/__tests__/event-helpers.test.ts +68 -0
- package/src/engine/__tests__/extends-registrar.test.ts +159 -0
- package/src/engine/__tests__/factories-long-text.test.ts +84 -0
- package/src/engine/__tests__/factories-time.test.ts +158 -0
- package/src/engine/__tests__/field-predicates.test.ts +48 -0
- package/src/engine/__tests__/hook-phases.test.ts +132 -0
- package/src/engine/__tests__/identifiers.test.ts +35 -0
- package/src/engine/__tests__/lifecycle-hooks.test.ts +237 -0
- package/src/engine/__tests__/nav.test.ts +267 -0
- package/src/engine/__tests__/ownership.test.ts +421 -0
- package/src/engine/__tests__/parse-ref-target.test.ts +43 -0
- package/src/engine/__tests__/projection-helpers.test.ts +62 -0
- package/src/engine/__tests__/projection.test.ts +191 -0
- package/src/engine/__tests__/qualified-name.test.ts +264 -0
- package/src/engine/__tests__/resolve-config-or-param.test.ts +315 -0
- package/src/engine/__tests__/run-in.test.ts +38 -0
- package/src/engine/__tests__/schema-builder.test.ts +380 -0
- package/src/engine/__tests__/screen.test.ts +408 -0
- package/src/engine/__tests__/state-machine.test.ts +148 -0
- package/src/engine/__tests__/system-user.test.ts +57 -0
- package/src/engine/__tests__/validation-hooks.test.ts +71 -0
- package/src/engine/access.ts +23 -0
- package/src/engine/boot-validator.ts +1528 -0
- package/src/engine/build-app-schema.ts +125 -0
- package/src/engine/config-helpers.ts +115 -0
- package/src/engine/constants.ts +85 -0
- package/src/engine/create-app.ts +98 -0
- package/src/engine/define-feature.ts +702 -0
- package/src/engine/define-handler.ts +78 -0
- package/src/engine/define-roles.ts +19 -0
- package/src/engine/effective-features.ts +87 -0
- package/src/engine/entity-handlers.ts +364 -0
- package/src/engine/event-helpers.ts +73 -0
- package/src/engine/factories.ts +328 -0
- package/src/engine/feature-ast/__tests__/canonical-form.test.ts +416 -0
- package/src/engine/feature-ast/__tests__/parse-happy-path.test.ts +197 -0
- package/src/engine/feature-ast/__tests__/parse-real-features.test.ts +128 -0
- package/src/engine/feature-ast/__tests__/parse.test.ts +888 -0
- package/src/engine/feature-ast/__tests__/patch.test.ts +360 -0
- package/src/engine/feature-ast/__tests__/patcher.test.ts +469 -0
- package/src/engine/feature-ast/__tests__/render-roundtrip.test.ts +287 -0
- package/src/engine/feature-ast/extractors.ts +2562 -0
- package/src/engine/feature-ast/index.ts +105 -0
- package/src/engine/feature-ast/parse.ts +369 -0
- package/src/engine/feature-ast/patch.ts +525 -0
- package/src/engine/feature-ast/patcher.ts +518 -0
- package/src/engine/feature-ast/patterns.ts +434 -0
- package/src/engine/feature-ast/render.ts +602 -0
- package/src/engine/feature-ast/source-location.ts +45 -0
- package/src/engine/field-access.ts +120 -0
- package/src/engine/index.ts +254 -0
- package/src/engine/ownership.ts +337 -0
- package/src/engine/parse-ref-target.ts +22 -0
- package/src/engine/pattern-library/__tests__/library.test.ts +351 -0
- package/src/engine/pattern-library/index.ts +24 -0
- package/src/engine/pattern-library/library.ts +1117 -0
- package/src/engine/pattern-library/types.ts +255 -0
- package/src/engine/projection-helpers.ts +85 -0
- package/src/engine/qualified-name.ts +122 -0
- package/src/engine/read-claim.ts +31 -0
- package/src/engine/registry.ts +1325 -0
- package/src/engine/resolve-config-or-param.ts +153 -0
- package/src/engine/run-in.ts +29 -0
- package/src/engine/schema-builder.ts +175 -0
- package/src/engine/screen-filter-ops.ts +51 -0
- package/src/engine/state-machine.ts +70 -0
- package/src/engine/system-user.ts +32 -0
- package/src/engine/types/config.ts +306 -0
- package/src/engine/types/event-type-map.ts +37 -0
- package/src/engine/types/feature.ts +574 -0
- package/src/engine/types/fields.ts +422 -0
- package/src/engine/types/handlers.ts +742 -0
- package/src/engine/types/hooks.ts +142 -0
- package/src/engine/types/http-route.ts +54 -0
- package/src/engine/types/identifiers.ts +47 -0
- package/src/engine/types/index.ts +208 -0
- package/src/engine/types/nav.ts +46 -0
- package/src/engine/types/projection.ts +132 -0
- package/src/engine/types/relations.ts +51 -0
- package/src/engine/types/screen.ts +452 -0
- package/src/engine/types/workspace.ts +42 -0
- package/src/engine/validation.ts +33 -0
- package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.ts +173 -0
- package/src/entrypoint/__tests__/split-deploy.integration.ts +297 -0
- package/src/entrypoint/index.ts +442 -0
- package/src/errors/__tests__/classes.test.ts +371 -0
- package/src/errors/__tests__/write-failures.test.ts +109 -0
- package/src/errors/classes.ts +249 -0
- package/src/errors/i18n/de.yaml +83 -0
- package/src/errors/i18n/en.yaml +80 -0
- package/src/errors/index.ts +41 -0
- package/src/errors/kumiko-error.ts +67 -0
- package/src/errors/reasons.ts +36 -0
- package/src/errors/serialize.ts +136 -0
- package/src/errors/transition-details.ts +30 -0
- package/src/errors/write-error-info.ts +123 -0
- package/src/errors/zod-bridge.ts +49 -0
- package/src/event-store/__tests__/admin-api.integration.ts +361 -0
- package/src/event-store/__tests__/event-store.integration.ts +584 -0
- package/src/event-store/__tests__/get-stream-version-perf.integration.ts +83 -0
- package/src/event-store/__tests__/perf.integration.ts +255 -0
- package/src/event-store/__tests__/snapshot.integration.ts +267 -0
- package/src/event-store/__tests__/upcaster-dead-letter.integration.ts +204 -0
- package/src/event-store/__tests__/upcaster.integration.ts +460 -0
- package/src/event-store/admin-api.ts +257 -0
- package/src/event-store/archive.ts +106 -0
- package/src/event-store/errors.ts +35 -0
- package/src/event-store/event-store.ts +405 -0
- package/src/event-store/events-schema.ts +90 -0
- package/src/event-store/index.ts +50 -0
- package/src/event-store/snapshot.ts +210 -0
- package/src/event-store/upcaster-dead-letter.ts +119 -0
- package/src/event-store/upcaster.ts +147 -0
- package/src/files/__tests__/content-disposition.test.ts +123 -0
- package/src/files/__tests__/file-field-column.integration.ts +103 -0
- package/src/files/__tests__/file-field-pipeline.integration.ts +211 -0
- package/src/files/__tests__/file-handle.test.ts +122 -0
- package/src/files/__tests__/files.integration.ts +830 -0
- package/src/files/__tests__/storage-tracking.integration.ts +153 -0
- package/src/files/content-disposition.ts +55 -0
- package/src/files/file-handle.ts +63 -0
- package/src/files/file-ref-table.ts +22 -0
- package/src/files/file-routes.ts +353 -0
- package/src/files/in-memory-provider.ts +62 -0
- package/src/files/index.ts +29 -0
- package/src/files/local-provider.ts +35 -0
- package/src/files/storage-tracking.ts +60 -0
- package/src/files/types.ts +118 -0
- package/src/i18n/__tests__/i18n.test.ts +72 -0
- package/src/i18n/index.ts +29 -0
- package/src/jobs/__tests__/job-event-trigger.integration.ts +172 -0
- package/src/jobs/__tests__/job-multi-trigger.integration.ts +144 -0
- package/src/jobs/__tests__/jobs.integration.ts +566 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-runner.ts +574 -0
- package/src/lifecycle/__tests__/create-test-lifecycle.ts +19 -0
- package/src/lifecycle/__tests__/lifecycle-server.integration.ts +108 -0
- package/src/lifecycle/__tests__/lifecycle.test.ts +212 -0
- package/src/lifecycle/__tests__/signal-handlers.test.ts +106 -0
- package/src/lifecycle/index.ts +13 -0
- package/src/lifecycle/lifecycle.ts +160 -0
- package/src/lifecycle/signal-handlers.ts +62 -0
- package/src/logging/__tests__/pino-trace-bridge.test.ts +50 -0
- package/src/logging/index.ts +3 -0
- package/src/logging/pino-logger.ts +64 -0
- package/src/logging/types.ts +7 -0
- package/src/migrations/__tests__/compare-snapshots.test.ts +150 -0
- package/src/migrations/__tests__/detect-drift.integration.ts +320 -0
- package/src/migrations/__tests__/detect-projections-to-rebuild.integration.ts +134 -0
- package/src/migrations/__tests__/rebuild-marker.test.ts +79 -0
- package/src/migrations/index.ts +28 -0
- package/src/migrations/projection-detection.ts +149 -0
- package/src/migrations/rebuild-marker.ts +64 -0
- package/src/migrations/schema-drift.ts +395 -0
- package/src/observability/__tests__/console-provider.test.ts +67 -0
- package/src/observability/__tests__/metric-validator.test.ts +87 -0
- package/src/observability/__tests__/noop-provider.test.ts +82 -0
- package/src/observability/__tests__/observability.integration.ts +559 -0
- package/src/observability/__tests__/prometheus-meter.test.ts +144 -0
- package/src/observability/__tests__/recording-meter.test.ts +101 -0
- package/src/observability/__tests__/recording-tracer.test.ts +110 -0
- package/src/observability/__tests__/sensitive-filter.test.ts +98 -0
- package/src/observability/console-provider.ts +130 -0
- package/src/observability/context.ts +26 -0
- package/src/observability/fallback.ts +34 -0
- package/src/observability/ids.ts +25 -0
- package/src/observability/index.ts +79 -0
- package/src/observability/metric-validator.ts +86 -0
- package/src/observability/metrics-handle.ts +56 -0
- package/src/observability/noop-provider.ts +146 -0
- package/src/observability/prometheus-meter.ts +284 -0
- package/src/observability/recording-meter.ts +156 -0
- package/src/observability/recording-tracer.ts +198 -0
- package/src/observability/redis-wrapper.ts +132 -0
- package/src/observability/sensitive-filter.ts +108 -0
- package/src/observability/standard-metrics.ts +213 -0
- package/src/observability/types/index.ts +29 -0
- package/src/observability/types/metric.ts +56 -0
- package/src/observability/types/provider.ts +32 -0
- package/src/observability/types/span.ts +64 -0
- package/src/pipeline/__tests__/archive-stream.integration.ts +220 -0
- package/src/pipeline/__tests__/auth-claims-resolver.test.ts +279 -0
- package/src/pipeline/__tests__/cascade-handler.integration.ts +419 -0
- package/src/pipeline/__tests__/cascade-handler.test.ts +52 -0
- package/src/pipeline/__tests__/causation-chain.integration.ts +206 -0
- package/src/pipeline/__tests__/ctx-bridge.integration.ts +234 -0
- package/src/pipeline/__tests__/dispatcher.test.ts +379 -0
- package/src/pipeline/__tests__/distributed-lock.integration.ts +67 -0
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +323 -0
- package/src/pipeline/__tests__/event-dedup.integration.ts +153 -0
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.ts +220 -0
- package/src/pipeline/__tests__/event-dispatcher-multi-instance.integration.ts +423 -0
- package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.ts +123 -0
- package/src/pipeline/__tests__/event-dispatcher-recovery.integration.ts +202 -0
- package/src/pipeline/__tests__/event-dispatcher-second-audit.integration.ts +290 -0
- package/src/pipeline/__tests__/event-dispatcher-strict.test.ts +65 -0
- package/src/pipeline/__tests__/event-dispatcher.integration.ts +287 -0
- package/src/pipeline/__tests__/event-retention.integration.ts +239 -0
- package/src/pipeline/__tests__/fetch-for-writing.integration.ts +281 -0
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +430 -0
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +266 -0
- package/src/pipeline/__tests__/msp-error-mode.integration.ts +149 -0
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +228 -0
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +368 -0
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +341 -0
- package/src/pipeline/__tests__/perf-rebuild.integration.ts +147 -0
- package/src/pipeline/__tests__/projection-rebuild.integration.ts +551 -0
- package/src/pipeline/__tests__/query-projection.integration.ts +201 -0
- package/src/pipeline/__tests__/redis-pipeline.integration.ts +306 -0
- package/src/pipeline/append-event-core.ts +117 -0
- package/src/pipeline/auth-claims-resolver.ts +103 -0
- package/src/pipeline/cascade-handler.ts +113 -0
- package/src/pipeline/dispatcher.ts +1585 -0
- package/src/pipeline/distributed-lock.ts +37 -0
- package/src/pipeline/entity-cache.ts +113 -0
- package/src/pipeline/event-consumer-state.ts +108 -0
- package/src/pipeline/event-dedup.ts +23 -0
- package/src/pipeline/event-dispatcher.ts +1016 -0
- package/src/pipeline/event-retention.ts +154 -0
- package/src/pipeline/idempotency.ts +76 -0
- package/src/pipeline/index.ts +66 -0
- package/src/pipeline/lifecycle-pipeline.ts +409 -0
- package/src/pipeline/msp-rebuild.ts +242 -0
- package/src/pipeline/multi-stream-apply-context.ts +115 -0
- package/src/pipeline/projection-rebuild.ts +334 -0
- package/src/pipeline/projection-state.ts +72 -0
- package/src/pipeline/projections-runner.ts +56 -0
- package/src/pipeline/redis-keys.ts +11 -0
- package/src/pipeline/system-hooks.ts +190 -0
- package/src/random/__tests__/generate.test.ts +149 -0
- package/src/random/generate.ts +141 -0
- package/src/random/index.ts +8 -0
- package/src/random/words.ts +392 -0
- package/src/rate-limit/__tests__/dispatcher-l3.integration.ts +111 -0
- package/src/rate-limit/__tests__/middleware.integration.ts +189 -0
- package/src/rate-limit/__tests__/resolver.integration.ts +189 -0
- package/src/rate-limit/bucket.ts +36 -0
- package/src/rate-limit/index.ts +14 -0
- package/src/rate-limit/middleware.ts +152 -0
- package/src/rate-limit/resolver.ts +267 -0
- package/src/redis/__tests__/redis-options.test.ts +54 -0
- package/src/redis/index.ts +74 -0
- package/src/search/__tests__/meilisearch-adapter.integration.ts +236 -0
- package/src/search/__tests__/search-adapter.test.ts +256 -0
- package/src/search/in-memory-adapter.ts +123 -0
- package/src/search/index.ts +12 -0
- package/src/search/meilisearch-adapter.ts +106 -0
- package/src/search/types.ts +39 -0
- package/src/secrets/__tests__/dek-cache.test.ts +213 -0
- package/src/secrets/__tests__/env-master-key-provider.test.ts +119 -0
- package/src/secrets/__tests__/envelope.test.ts +74 -0
- package/src/secrets/__tests__/leak-guard.test.ts +92 -0
- package/src/secrets/__tests__/rotation.test.ts +149 -0
- package/src/secrets/dek-cache.ts +116 -0
- package/src/secrets/env-master-key-provider.ts +162 -0
- package/src/secrets/envelope.ts +55 -0
- package/src/secrets/index.ts +19 -0
- package/src/secrets/leak-guard.ts +87 -0
- package/src/secrets/rotation.ts +34 -0
- package/src/secrets/types.ts +107 -0
- package/src/stack/db.ts +104 -0
- package/src/stack/event-collector.ts +23 -0
- package/src/stack/index.ts +32 -0
- package/src/stack/redis.ts +44 -0
- package/src/stack/request-helper.ts +168 -0
- package/src/stack/table-helpers.ts +104 -0
- package/src/stack/test-stack.ts +357 -0
- package/src/stack/test-users.ts +37 -0
- package/src/testing/__tests__/e2e-generator.test.ts +230 -0
- package/src/testing/__tests__/ensure-entity-table.integration.ts +54 -0
- package/src/testing/access-assertions.ts +15 -0
- package/src/testing/assertions.ts +35 -0
- package/src/testing/e2e-generator.ts +465 -0
- package/src/testing/expect-error.ts +25 -0
- package/src/testing/handler-context.ts +125 -0
- package/src/testing/http-cookies.ts +52 -0
- package/src/testing/index.ts +41 -0
- package/src/testing/late-bound.ts +39 -0
- package/src/testing/mutable-master-key-provider.ts +31 -0
- package/src/testing/observability-recorder.ts +54 -0
- package/src/testing/shared-entities.ts +49 -0
- package/src/testing/utils.ts +1 -0
- package/src/testing/wait-for.ts +31 -0
- package/src/time/__tests__/polyfill.test.ts +73 -0
- package/src/time/__tests__/tz-context.test.ts +121 -0
- package/src/time/index.ts +21 -0
- package/src/time/polyfill.ts +70 -0
- package/src/time/tz-context.ts +107 -0
- package/src/ui-types/app-schema.ts +57 -0
- package/src/ui-types/index.ts +65 -0
- package/src/utils/__tests__/assert.test.ts +17 -0
- package/src/utils/__tests__/env-parse.test.ts +54 -0
- package/src/utils/assert.ts +18 -0
- package/src/utils/env-parse.ts +16 -0
- package/src/utils/ids.ts +16 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/safe-json.ts +30 -0
- package/src/utils/serialization.ts +7 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { and, getTableName, inArray, lt, sql } from "drizzle-orm";
|
|
2
|
+
import type { DbConnection } from "../db/connection";
|
|
3
|
+
import { eventsTable } from "../event-store";
|
|
4
|
+
import { eventConsumerStateTable } from "./event-consumer-state";
|
|
5
|
+
|
|
6
|
+
// Retention for the events-table. Aggregate events are source of truth —
|
|
7
|
+
// they power loadAggregate, projection rebuilds, asOf queries, audit.
|
|
8
|
+
// Pruning them is destructive and cannot be reversed, so the caller MUST
|
|
9
|
+
// name the aggregateTypes explicitly; there is no default.
|
|
10
|
+
//
|
|
11
|
+
// Typical use: ops prunes archived aggregate streams (see archive.ts) after
|
|
12
|
+
// legal/compliance retention has elapsed, or drops a specific aggregate
|
|
13
|
+
// type that's been deprecated and replaced.
|
|
14
|
+
//
|
|
15
|
+
// Safety guard: before deleting, we check every row in
|
|
16
|
+
// `kumiko_event_consumers`. If the minimum `lastProcessedEventId` across
|
|
17
|
+
// non-disabled consumers is below the largest event id we'd delete, we
|
|
18
|
+
// refuse with ConsumerLagError. A lagging consumer must either catch up,
|
|
19
|
+
// be disabled, or the retention call must opt around it; pruning past its
|
|
20
|
+
// cursor would silently drop deliveries.
|
|
21
|
+
//
|
|
22
|
+
// No background scheduler: the framework exposes the function. Ops wires
|
|
23
|
+
// it into a cron, BullMQ repeating job, or pg_cron entry — whatever the
|
|
24
|
+
// deployment already runs. That keeps the framework dependency-free for
|
|
25
|
+
// retention and lets ops reason about timing alongside existing jobs.
|
|
26
|
+
|
|
27
|
+
export type PruneEventsOptions = {
|
|
28
|
+
// Delete events whose createdAt is strictly older than this.
|
|
29
|
+
// Pass EITHER olderThan (explicit Temporal.Instant) OR olderThanDays.
|
|
30
|
+
readonly olderThan?: Temporal.Instant;
|
|
31
|
+
readonly olderThanDays?: number;
|
|
32
|
+
// Which aggregateTypes to prune. REQUIRED and non-empty. There is no
|
|
33
|
+
// default — pruning the event log is destructive, so the caller has to
|
|
34
|
+
// name what they're destroying.
|
|
35
|
+
readonly aggregateTypes: readonly string[];
|
|
36
|
+
// Dry-run: compute what would be deleted, return count, delete nothing.
|
|
37
|
+
readonly dryRun?: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type PruneEventsResult = {
|
|
41
|
+
readonly deletedCount: number;
|
|
42
|
+
readonly cutoff: Temporal.Instant;
|
|
43
|
+
readonly aggregateTypes: readonly string[];
|
|
44
|
+
readonly dryRun: boolean;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export class ConsumerLagError extends Error {
|
|
48
|
+
constructor(
|
|
49
|
+
readonly laggingConsumer: string,
|
|
50
|
+
readonly consumerCursor: bigint,
|
|
51
|
+
readonly maxCandidateId: bigint,
|
|
52
|
+
) {
|
|
53
|
+
super(
|
|
54
|
+
`Consumer "${laggingConsumer}" is behind the prune candidates ` +
|
|
55
|
+
`(cursor=${consumerCursor}, max candidate event id=${maxCandidateId}). ` +
|
|
56
|
+
`Pruning would silently drop deliveries. Catch up, disable, or skip the consumer first.`,
|
|
57
|
+
);
|
|
58
|
+
this.name = "ConsumerLagError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveCutoff(opts: PruneEventsOptions): Temporal.Instant {
|
|
63
|
+
if (opts.olderThan) return opts.olderThan;
|
|
64
|
+
const days = opts.olderThanDays;
|
|
65
|
+
if (days === undefined || days <= 0) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
"pruneEvents: pass olderThan (Temporal.Instant) or olderThanDays (positive number).",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return Temporal.Now.instant().subtract({ hours: days * 24 });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function pruneEvents(
|
|
74
|
+
db: DbConnection,
|
|
75
|
+
options: PruneEventsOptions,
|
|
76
|
+
): Promise<PruneEventsResult> {
|
|
77
|
+
const cutoff = resolveCutoff(options);
|
|
78
|
+
if (!options.aggregateTypes || options.aggregateTypes.length === 0) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"pruneEvents: aggregateTypes is required and must be non-empty. Pruning the event log is destructive — name the aggregate types to delete explicitly.",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const aggregateTypes = options.aggregateTypes;
|
|
84
|
+
const dryRun = options.dryRun === true;
|
|
85
|
+
|
|
86
|
+
return db.transaction(async (tx) => {
|
|
87
|
+
// Serialise against consumer-bootstrap INSERTs. Without this, the race
|
|
88
|
+
// is: prune reads consumers (snapshot misses a consumer bootstrapping
|
|
89
|
+
// in a parallel tx) → consumer commits its row with
|
|
90
|
+
// lastProcessedEventId=0 → prune deletes events below its new cursor
|
|
91
|
+
// → first pass of the new consumer silently skips the deleted events.
|
|
92
|
+
//
|
|
93
|
+
// SHARE is the lightest table-mode that conflicts with ROW EXCLUSIVE
|
|
94
|
+
// (the mode INSERT/UPDATE/DELETE take). Concurrent INSERTs on the
|
|
95
|
+
// consumer table queue until this TX commits; concurrent UPDATEs
|
|
96
|
+
// (cursor advances) do too, but prune is measured in milliseconds and
|
|
97
|
+
// pausing cursor advances for that window is cheap insurance against
|
|
98
|
+
// a silent data-loss bug.
|
|
99
|
+
//
|
|
100
|
+
// Drizzle can't express LOCK TABLE — drop to raw SQL with the table
|
|
101
|
+
// name identifier so a future table-rename is caught at compile time.
|
|
102
|
+
await tx.execute(sql.raw(`LOCK TABLE ${getTableName(eventConsumerStateTable)} IN SHARE MODE`));
|
|
103
|
+
|
|
104
|
+
// Step 1 — collect candidate event ids.
|
|
105
|
+
const candidates = await tx
|
|
106
|
+
.select({ id: eventsTable.id })
|
|
107
|
+
.from(eventsTable)
|
|
108
|
+
.where(
|
|
109
|
+
and(
|
|
110
|
+
inArray(eventsTable.aggregateType, [...aggregateTypes]),
|
|
111
|
+
lt(eventsTable.createdAt, cutoff),
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (candidates.length === 0) {
|
|
116
|
+
return { deletedCount: 0, cutoff, aggregateTypes, dryRun };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const maxCandidateId = candidates.reduce(
|
|
120
|
+
(acc, row) => (row.id > acc ? row.id : acc),
|
|
121
|
+
candidates[0]?.id ?? 0n,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Step 2 — safety guard: check every ACTIVE consumer has moved past
|
|
125
|
+
// the candidate set. Disabled consumers are intentionally excluded —
|
|
126
|
+
// ops disables them precisely to park them while pruning happens.
|
|
127
|
+
// The SHARE lock above guarantees this SELECT sees a complete view:
|
|
128
|
+
// no new consumer can INSERT a fresh-cursor row between here and the
|
|
129
|
+
// DELETE below.
|
|
130
|
+
const activeConsumers = await tx
|
|
131
|
+
.select()
|
|
132
|
+
.from(eventConsumerStateTable)
|
|
133
|
+
.where(sql`${eventConsumerStateTable.status} <> 'disabled'`);
|
|
134
|
+
|
|
135
|
+
for (const consumer of activeConsumers) {
|
|
136
|
+
if (consumer.lastProcessedEventId < maxCandidateId) {
|
|
137
|
+
throw new ConsumerLagError(consumer.name, consumer.lastProcessedEventId, maxCandidateId);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (dryRun) {
|
|
142
|
+
return { deletedCount: candidates.length, cutoff, aggregateTypes, dryRun: true };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Step 3 — actual delete, bounded to the candidate set.
|
|
146
|
+
const candidateIds = candidates.map((c) => c.id);
|
|
147
|
+
const deleted = await tx
|
|
148
|
+
.delete(eventsTable)
|
|
149
|
+
.where(inArray(eventsTable.id, candidateIds))
|
|
150
|
+
.returning({ id: eventsTable.id });
|
|
151
|
+
|
|
152
|
+
return { deletedCount: deleted.length, cutoff, aggregateTypes, dryRun: false };
|
|
153
|
+
});
|
|
154
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type Redis from "ioredis";
|
|
2
|
+
import { RedisKeys } from "./redis-keys";
|
|
3
|
+
|
|
4
|
+
export type IdempotencyGuard = {
|
|
5
|
+
check(requestId: string): Promise<string | null>;
|
|
6
|
+
store(requestId: string, result: unknown): Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Sentinel stored under the key while the handler is running. A second
|
|
10
|
+
// request that sees this waits for the real result instead of racing.
|
|
11
|
+
const PENDING_MARKER = "__pending__";
|
|
12
|
+
|
|
13
|
+
export function createIdempotencyGuard(
|
|
14
|
+
redis: Redis,
|
|
15
|
+
options: {
|
|
16
|
+
ttlSeconds?: number;
|
|
17
|
+
pendingTtlSeconds?: number;
|
|
18
|
+
waitTimeoutMs?: number;
|
|
19
|
+
pollIntervalMs?: number;
|
|
20
|
+
} = {},
|
|
21
|
+
): IdempotencyGuard {
|
|
22
|
+
const ttl = options.ttlSeconds ?? 300;
|
|
23
|
+
// Max time a single handler is allowed to hold the in-progress lock before
|
|
24
|
+
// a parallel retry is allowed to try again. Short enough that a crashed
|
|
25
|
+
// handler doesn't permanently block retries, long enough to cover normal
|
|
26
|
+
// batch durations.
|
|
27
|
+
const pendingTtl = options.pendingTtlSeconds ?? 30;
|
|
28
|
+
const waitTimeoutMs = options.waitTimeoutMs ?? 25_000;
|
|
29
|
+
const pollIntervalMs = options.pollIntervalMs ?? 50;
|
|
30
|
+
const prefix = RedisKeys.idempotency;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
// Returns:
|
|
34
|
+
// null — caller owns the in-progress lock, proceed to run the handler
|
|
35
|
+
// and then call store() with the real result.
|
|
36
|
+
// string — serialized cached result from a concurrent or prior request.
|
|
37
|
+
//
|
|
38
|
+
// The old behaviour (pure GET + SET-NX-store) let two parallel requests
|
|
39
|
+
// both see a cache miss, both execute side-effects, and only one persist
|
|
40
|
+
// the result. This version uses a pending-marker lock so the second caller
|
|
41
|
+
// waits for the first to finish and reuses its result.
|
|
42
|
+
async check(requestId) {
|
|
43
|
+
const key = `${prefix}${requestId}`;
|
|
44
|
+
|
|
45
|
+
// Try to acquire the in-progress lock.
|
|
46
|
+
const acquired = await redis.set(key, PENDING_MARKER, "EX", pendingTtl, "NX");
|
|
47
|
+
if (acquired === "OK") return null;
|
|
48
|
+
|
|
49
|
+
// Lost the race. Poll until the lock holder stores a result, or the
|
|
50
|
+
// lock expires (handler crashed) and we can try again.
|
|
51
|
+
const deadline = Date.now() + waitTimeoutMs;
|
|
52
|
+
while (Date.now() < deadline) {
|
|
53
|
+
const value = await redis.get(key);
|
|
54
|
+
if (value === null) {
|
|
55
|
+
// Lock expired before a result was stored — try to claim it
|
|
56
|
+
// ourselves and proceed as the new owner.
|
|
57
|
+
const reclaimed = await redis.set(key, PENDING_MARKER, "EX", pendingTtl, "NX");
|
|
58
|
+
if (reclaimed === "OK") return null;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (value !== PENDING_MARKER) return value;
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Gave up waiting. Treat as a fresh request — forces the caller to
|
|
66
|
+
// re-run the handler rather than hang indefinitely.
|
|
67
|
+
return null;
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async store(requestId, result) {
|
|
71
|
+
// Overwrite the pending marker with the real result. Plain SET (no NX)
|
|
72
|
+
// on purpose: we own the lock; writing the result is the final step.
|
|
73
|
+
await redis.set(`${prefix}${requestId}`, JSON.stringify(result), "EX", ttl);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export type { ResolveAuthClaimsArgs } from "./auth-claims-resolver";
|
|
2
|
+
export { resolveAuthClaims } from "./auth-claims-resolver";
|
|
3
|
+
export { createCascadeDeleteHook } from "./cascade-handler";
|
|
4
|
+
export type { Dispatcher } from "./dispatcher";
|
|
5
|
+
export { createDispatcher } from "./dispatcher";
|
|
6
|
+
export type { DistributedLock } from "./distributed-lock";
|
|
7
|
+
export { createDistributedLock } from "./distributed-lock";
|
|
8
|
+
export type { EntityCache, EntityCacheOptions } from "./entity-cache";
|
|
9
|
+
export { createEntityCache } from "./entity-cache";
|
|
10
|
+
export type { ConsumerStatus } from "./event-consumer-state";
|
|
11
|
+
export {
|
|
12
|
+
CONSUMER_STATUSES,
|
|
13
|
+
ConsumerStatuses,
|
|
14
|
+
createEventConsumerStateTable,
|
|
15
|
+
eventConsumerStateTable,
|
|
16
|
+
} from "./event-consumer-state";
|
|
17
|
+
export type { EventDedup } from "./event-dedup";
|
|
18
|
+
export { createEventDedup } from "./event-dedup";
|
|
19
|
+
export type {
|
|
20
|
+
ConsumerProgress,
|
|
21
|
+
ConsumerRecoveryState,
|
|
22
|
+
DispatcherPassResult,
|
|
23
|
+
EventConsumer,
|
|
24
|
+
EventConsumerHandler,
|
|
25
|
+
EventDispatcher,
|
|
26
|
+
EventDispatcherOptions,
|
|
27
|
+
} from "./event-dispatcher";
|
|
28
|
+
export {
|
|
29
|
+
createEventDispatcher,
|
|
30
|
+
disableConsumer,
|
|
31
|
+
enableConsumer,
|
|
32
|
+
getAllConsumerProgress,
|
|
33
|
+
getConsumerState,
|
|
34
|
+
listConsumersWithState,
|
|
35
|
+
restartConsumer,
|
|
36
|
+
skipPoisonEvent,
|
|
37
|
+
} from "./event-dispatcher";
|
|
38
|
+
export type { PruneEventsOptions, PruneEventsResult } from "./event-retention";
|
|
39
|
+
export { ConsumerLagError, pruneEvents } from "./event-retention";
|
|
40
|
+
export type { IdempotencyGuard } from "./idempotency";
|
|
41
|
+
export { createIdempotencyGuard } from "./idempotency";
|
|
42
|
+
export type { LifecycleHooks, SystemHookDef, SystemHooks } from "./lifecycle-pipeline";
|
|
43
|
+
export { createLifecycleHooks } from "./lifecycle-pipeline";
|
|
44
|
+
export type { MspRebuildDeps } from "./msp-rebuild";
|
|
45
|
+
export { rebuildMultiStreamProjection } from "./msp-rebuild";
|
|
46
|
+
export type { ProjectionProgress, RebuildResult } from "./projection-rebuild";
|
|
47
|
+
export {
|
|
48
|
+
getAllProjectionProgress,
|
|
49
|
+
getProjectionState,
|
|
50
|
+
listProjectionsWithState,
|
|
51
|
+
rebuildProjection,
|
|
52
|
+
} from "./projection-rebuild";
|
|
53
|
+
export type { ProjectionStatus } from "./projection-state";
|
|
54
|
+
export {
|
|
55
|
+
createProjectionStateTable,
|
|
56
|
+
PROJECTION_STATUSES,
|
|
57
|
+
ProjectionStatuses,
|
|
58
|
+
projectionStateTable,
|
|
59
|
+
} from "./projection-state";
|
|
60
|
+
export { runProjectionsForEvent } from "./projections-runner";
|
|
61
|
+
export {
|
|
62
|
+
createSearchEventConsumer,
|
|
63
|
+
createSseBroadcastEventConsumer,
|
|
64
|
+
SEARCH_CONSUMER_NAME,
|
|
65
|
+
SSE_BROADCAST_CONSUMER_NAME,
|
|
66
|
+
} from "./system-hooks";
|