@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { checkWriteFieldRoles, filterReadFields } from "../field-access";
|
|
2
|
+
import { checkWriteFieldRoles, filterReadFields, PII_MASKED_VALUE } from "../field-access";
|
|
3
3
|
import type { EntityDefinition } from "../types";
|
|
4
4
|
|
|
5
5
|
const entity: EntityDefinition = {
|
|
@@ -25,6 +25,28 @@ describe("filterReadFields", () => {
|
|
|
25
25
|
const filtered = filterReadFields(entity, row, admin);
|
|
26
26
|
expect(filtered["secret"]).toBe("visible");
|
|
27
27
|
});
|
|
28
|
+
|
|
29
|
+
test("masks piiEncrypted fields instead of stripping them (kumiko-platform#463)", () => {
|
|
30
|
+
const entityWithPii: EntityDefinition = {
|
|
31
|
+
fields: {
|
|
32
|
+
...entity.fields,
|
|
33
|
+
iban: {
|
|
34
|
+
type: "text",
|
|
35
|
+
piiEncrypted: true,
|
|
36
|
+
tenantOwned: true,
|
|
37
|
+
access: { read: { admin: "all" } },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const row = { id: 1, title: "Hello", secret: "hidden", iban: "DE89370400440532013000" };
|
|
42
|
+
|
|
43
|
+
const filteredForEditor = filterReadFields(entityWithPii, row, editor);
|
|
44
|
+
expect(filteredForEditor["secret"]).toBeUndefined();
|
|
45
|
+
expect(filteredForEditor["iban"]).toBe(PII_MASKED_VALUE);
|
|
46
|
+
|
|
47
|
+
const filteredForAdmin = filterReadFields(entityWithPii, row, admin);
|
|
48
|
+
expect(filteredForAdmin["iban"]).toBe("DE89370400440532013000");
|
|
49
|
+
});
|
|
28
50
|
});
|
|
29
51
|
|
|
30
52
|
describe("checkWriteFieldRoles", () => {
|
|
@@ -59,11 +59,11 @@ describe("HookPhases defaults", () => {
|
|
|
59
59
|
expect(entry?.[0]?.phase).toBe(HookPhases.inTransaction);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
test("
|
|
62
|
+
test("hook({allOf}) postSave defaults to afterCommit, preDelete is forced inTransaction", () => {
|
|
63
63
|
const feature = defineFeature("test", (r) => {
|
|
64
64
|
const thing = r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
65
|
-
r.
|
|
66
|
-
r.
|
|
65
|
+
r.hook("postSave", { allOf: thing }, noopSave);
|
|
66
|
+
r.hook("preDelete", { allOf: thing }, async () => undefined);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
expect(feature.entityHooks?.postSave?.["thing"]?.[0]?.phase).toBe(HookPhases.afterCommit);
|
|
@@ -120,8 +120,8 @@ describe("Registry phase filtering", () => {
|
|
|
120
120
|
|
|
121
121
|
const feature = defineFeature("test", (r) => {
|
|
122
122
|
const thing = r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
123
|
-
r.
|
|
124
|
-
r.
|
|
123
|
+
r.hook("postSave", { allOf: thing }, inTxFn, { phase: HookPhases.inTransaction });
|
|
124
|
+
r.hook("postSave", { allOf: thing }, afterFn);
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
const registry = createRegistry([feature]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
+
buildSessionRoles,
|
|
3
4
|
FORBIDDEN_MEMBERSHIP_ROLES,
|
|
4
5
|
findForbiddenMembershipRole,
|
|
5
6
|
isForbiddenMembershipRole,
|
|
@@ -41,23 +42,16 @@ describe("forbidden membership roles", () => {
|
|
|
41
42
|
// ONLY the membership portion, never the merged result — so a legitimate
|
|
42
43
|
// SystemAdmin in globalRoles survives, a resurrected one in membership does not.
|
|
43
44
|
describe("merge semantics (globalRoles never filtered)", () => {
|
|
44
|
-
function merge(
|
|
45
|
-
globalRoles: readonly string[],
|
|
46
|
-
membershipRoles: readonly string[],
|
|
47
|
-
): readonly string[] {
|
|
48
|
-
return Array.from(new Set([...globalRoles, ...stripForbiddenMembershipRoles(membershipRoles)]));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
45
|
test("global SystemAdmin survives (no regression for real admins)", () => {
|
|
52
|
-
expect(
|
|
46
|
+
expect(buildSessionRoles(["SystemAdmin"], [])).toContain("SystemAdmin");
|
|
53
47
|
});
|
|
54
48
|
|
|
55
49
|
test("membership SystemAdmin is stripped (resurrected role neutralised)", () => {
|
|
56
|
-
expect(
|
|
50
|
+
expect(buildSessionRoles([], ["SystemAdmin"])).not.toContain("SystemAdmin");
|
|
57
51
|
});
|
|
58
52
|
|
|
59
53
|
test("global admin + tenant membership keeps both, deduped", () => {
|
|
60
|
-
expect([...
|
|
54
|
+
expect([...buildSessionRoles(["SystemAdmin"], ["Admin", "SystemAdmin"])].sort()).toEqual(
|
|
61
55
|
["Admin", "SystemAdmin"].sort(),
|
|
62
56
|
);
|
|
63
57
|
});
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
2
|
+
import { withBootValidatorFixture } from "../../testing/boot-validator-fixture";
|
|
3
|
+
import { validateBoot as validateBootRaw } from "../boot-validator";
|
|
3
4
|
import { defineFeature } from "../define-feature";
|
|
4
5
|
import { createEntity, createTextField } from "../factories";
|
|
5
6
|
import { createRegistry } from "../registry";
|
|
6
7
|
|
|
8
|
+
function validateBoot(features: Parameters<typeof validateBootRaw>[0]): void {
|
|
9
|
+
validateBootRaw(withBootValidatorFixture(features));
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
function productEntity() {
|
|
8
13
|
return createEntity({
|
|
9
14
|
table: "products",
|
|
@@ -75,6 +80,86 @@ describe("r.nav() — registration", () => {
|
|
|
75
80
|
});
|
|
76
81
|
});
|
|
77
82
|
|
|
83
|
+
describe("r.screen({ nav }) — inline nav sugar", () => {
|
|
84
|
+
test("synthesizes a nav entry from the screen's id", () => {
|
|
85
|
+
const feature = defineFeature("shop", (r) => {
|
|
86
|
+
r.entity("product", productEntity());
|
|
87
|
+
r.screen({
|
|
88
|
+
id: "products",
|
|
89
|
+
type: "entityList",
|
|
90
|
+
entity: "product",
|
|
91
|
+
columns: ["name"],
|
|
92
|
+
nav: { label: "shop:nav.products", icon: "box", order: 5 },
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
expect(feature.navs["products"]).toMatchObject({
|
|
96
|
+
id: "products",
|
|
97
|
+
label: "shop:nav.products",
|
|
98
|
+
icon: "box",
|
|
99
|
+
order: 5,
|
|
100
|
+
screen: "shop:screen:products",
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("supports parent like a standalone r.nav()", () => {
|
|
105
|
+
const feature = defineFeature("shop", (r) => {
|
|
106
|
+
r.nav({ id: "catalog", label: "x" });
|
|
107
|
+
r.entity("product", productEntity());
|
|
108
|
+
r.screen({
|
|
109
|
+
id: "products",
|
|
110
|
+
type: "entityList",
|
|
111
|
+
entity: "product",
|
|
112
|
+
columns: ["name"],
|
|
113
|
+
nav: { label: "y", parent: "shop:nav:catalog" },
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
expect(feature.navs["products"]?.parent).toBe("shop:nav:catalog");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("passes the same boot-validation as a standalone r.nav()", () => {
|
|
120
|
+
const feature = defineFeature("shop", (r) => {
|
|
121
|
+
r.entity("product", productEntity());
|
|
122
|
+
r.screen({
|
|
123
|
+
id: "products",
|
|
124
|
+
type: "entityList",
|
|
125
|
+
entity: "product",
|
|
126
|
+
columns: ["name"],
|
|
127
|
+
nav: { label: "y" },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
expect(() => validateBoot([feature])).not.toThrow();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("screen without nav registers no nav entry", () => {
|
|
134
|
+
const feature = defineFeature("shop", (r) => {
|
|
135
|
+
r.entity("product", productEntity());
|
|
136
|
+
r.screen({
|
|
137
|
+
id: "products",
|
|
138
|
+
type: "entityList",
|
|
139
|
+
entity: "product",
|
|
140
|
+
columns: ["name"],
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
expect(feature.navs["products"]).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("rejects when a standalone r.nav() already used the screen's id", () => {
|
|
147
|
+
expect(() =>
|
|
148
|
+
defineFeature("shop", (r) => {
|
|
149
|
+
r.nav({ id: "products", label: "standalone" });
|
|
150
|
+
r.entity("product", productEntity());
|
|
151
|
+
r.screen({
|
|
152
|
+
id: "products",
|
|
153
|
+
type: "entityList",
|
|
154
|
+
entity: "product",
|
|
155
|
+
columns: ["name"],
|
|
156
|
+
nav: { label: "inline" },
|
|
157
|
+
});
|
|
158
|
+
}),
|
|
159
|
+
).toThrow(/already registered/);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
78
163
|
describe("createRegistry — nav indexing", () => {
|
|
79
164
|
test("indexes nav entries by qualified name", () => {
|
|
80
165
|
const feature = defineFeature("shop", (r) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Pipeline-engine unit tests — defineWriteHandler({ perform:
|
|
1
|
+
// Pipeline-engine unit tests — defineWriteHandler({ perform: stepsPipeline(...) })
|
|
2
2
|
// boundary, run-pipeline runner contract, defineStep registry guards.
|
|
3
3
|
// Sub-step-builders (branch, forEach) live in pipeline-sub-pipelines.test.ts;
|
|
4
4
|
// boot-validator in validate-projection-allowlist.test.ts.
|
|
@@ -9,7 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
import { TestUsers } from "../../stack";
|
|
10
10
|
import { defineWriteHandler } from "../define-handler";
|
|
11
11
|
import { defineStep } from "../define-step";
|
|
12
|
-
import {
|
|
12
|
+
import { stepsPipeline } from "../pipeline";
|
|
13
13
|
import type { WriteEvent } from "../types/handlers";
|
|
14
14
|
import { buildMinimalCtx } from "./_pipeline-test-utils";
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@ describe("pipeline engine (return / compute / registry guards)", () => {
|
|
|
19
19
|
name: "demo:noop",
|
|
20
20
|
schema: z.object({ greeting: z.string() }),
|
|
21
21
|
access: { roles: ["User"] },
|
|
22
|
-
perform:
|
|
22
|
+
perform: stepsPipeline<{ greeting: string }, { echoed: string }>(({ event, r }) => [
|
|
23
23
|
r.step.return(() => ({
|
|
24
24
|
isSuccess: true as const,
|
|
25
25
|
data: { echoed: event.payload.greeting },
|
|
@@ -49,7 +49,7 @@ describe("pipeline engine (return / compute / registry guards)", () => {
|
|
|
49
49
|
name: "demo:static",
|
|
50
50
|
schema: z.object({}),
|
|
51
51
|
access: { roles: ["User"] },
|
|
52
|
-
perform:
|
|
52
|
+
perform: stepsPipeline<Record<string, never>, { ok: boolean }>(({ r }) => [
|
|
53
53
|
r.step.return({ isSuccess: true as const, data: { ok: true } }),
|
|
54
54
|
]),
|
|
55
55
|
});
|
|
@@ -89,7 +89,7 @@ describe("pipeline engine (return / compute / registry guards)", () => {
|
|
|
89
89
|
name: "demo:no-return",
|
|
90
90
|
schema: z.object({}),
|
|
91
91
|
access: { roles: ["User"] },
|
|
92
|
-
perform:
|
|
92
|
+
perform: stepsPipeline<Record<string, never>, never>(() => []),
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
await expect(
|
|
@@ -105,7 +105,7 @@ describe("pipeline engine (return / compute / registry guards)", () => {
|
|
|
105
105
|
name: "demo:thread",
|
|
106
106
|
schema: z.object({ base: z.number() }),
|
|
107
107
|
access: { roles: ["User"] },
|
|
108
|
-
perform:
|
|
108
|
+
perform: stepsPipeline<{ base: number }, { sum: number }>(({ event, r }) => [
|
|
109
109
|
r.step.compute("offset", () => 10),
|
|
110
110
|
r.step.compute("doubledBase", () => event.payload.base * 2),
|
|
111
111
|
r.step.return(({ steps }) => ({
|
|
@@ -133,7 +133,7 @@ describe("pipeline engine (return / compute / registry guards)", () => {
|
|
|
133
133
|
name: "demo:fresh",
|
|
134
134
|
schema: z.object({}),
|
|
135
135
|
access: { roles: ["User"] },
|
|
136
|
-
perform:
|
|
136
|
+
perform: stepsPipeline<Record<string, never>, { tick: number }>(({ r }) => [
|
|
137
137
|
r.step.compute("tick", () => ++counter),
|
|
138
138
|
r.step.return(({ steps }) => ({
|
|
139
139
|
isSuccess: true as const,
|
|
@@ -161,7 +161,7 @@ describe("pipeline engine (return / compute / registry guards)", () => {
|
|
|
161
161
|
name: "demo:unknown-kind",
|
|
162
162
|
schema: z.object({}),
|
|
163
163
|
access: { roles: ["User"] },
|
|
164
|
-
perform:
|
|
164
|
+
perform: stepsPipeline<Record<string, never>, never>(() => [
|
|
165
165
|
// Hand-crafted instance with a kind that's never been registered —
|
|
166
166
|
// simulates a typo in a future step-builder factory.
|
|
167
167
|
{ kind: "this-step-does-not-exist", args: {} },
|
|
@@ -190,7 +190,7 @@ describe("pipeline engine (return / compute / registry guards)", () => {
|
|
|
190
190
|
schema: z.object({}),
|
|
191
191
|
access: { roles: ["User"] },
|
|
192
192
|
handler: async () => ({ isSuccess: true as const, data: {} }),
|
|
193
|
-
perform:
|
|
193
|
+
perform: stepsPipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
194
194
|
r.step.return({ isSuccess: true as const, data: { ok: true } }),
|
|
195
195
|
]),
|
|
196
196
|
} as unknown as Parameters<typeof defineWriteHandler>[0];
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
import { defineFeature } from "../define-feature";
|
|
61
61
|
import { defineWriteHandler } from "../define-handler";
|
|
62
62
|
import { createEntity, createTextField } from "../factories";
|
|
63
|
-
import {
|
|
63
|
+
import { stepsPipeline } from "../pipeline";
|
|
64
64
|
|
|
65
65
|
const echoSchema = z.object({ greeting: z.string() });
|
|
66
66
|
|
|
@@ -70,7 +70,7 @@ const echoHandler = defineWriteHandler({
|
|
|
70
70
|
name: "echo",
|
|
71
71
|
schema: echoSchema,
|
|
72
72
|
access: { roles: ["Admin"] },
|
|
73
|
-
perform:
|
|
73
|
+
perform: stepsPipeline<z.infer<typeof echoSchema>, { echoed: string; from: string }>(
|
|
74
74
|
({ event, r }) => [
|
|
75
75
|
r.step.return(() => ({
|
|
76
76
|
isSuccess: true as const,
|
|
@@ -90,7 +90,7 @@ const explodeHandler = defineWriteHandler({
|
|
|
90
90
|
name: "explode",
|
|
91
91
|
schema: explodeSchema,
|
|
92
92
|
access: { roles: ["Admin"] },
|
|
93
|
-
perform:
|
|
93
|
+
perform: stepsPipeline<z.infer<typeof explodeSchema>, never>(({ r }) => [
|
|
94
94
|
r.step.return(() => {
|
|
95
95
|
throw new Error("boom");
|
|
96
96
|
}),
|
|
@@ -106,7 +106,7 @@ const compoundHandler = defineWriteHandler({
|
|
|
106
106
|
name: "compound",
|
|
107
107
|
schema: compoundSchema,
|
|
108
108
|
access: { roles: ["Admin"] },
|
|
109
|
-
perform:
|
|
109
|
+
perform: stepsPipeline<z.infer<typeof compoundSchema>, { sum: number; userId: string }>(
|
|
110
110
|
({ event, r }) => [
|
|
111
111
|
r.step.compute("offset", () => 100),
|
|
112
112
|
r.step.compute("doubledBase", () => event.payload.base * 2),
|
|
@@ -147,7 +147,7 @@ const logHandler = defineWriteHandler({
|
|
|
147
147
|
schema: logSchema,
|
|
148
148
|
access: { roles: ["Admin"] },
|
|
149
149
|
// Outer-`event`-capture pattern — see compoundHandler above for the why.
|
|
150
|
-
perform:
|
|
150
|
+
perform: stepsPipeline<z.infer<typeof logSchema>, { correlationId: string }>(({ event, r }) => [
|
|
151
151
|
r.step.unsafeProjectionUpsert({
|
|
152
152
|
table: pipelineDemoLogTable,
|
|
153
153
|
on: ["correlationId"],
|
|
@@ -182,7 +182,7 @@ const widgetCreateHandler = defineWriteHandler({
|
|
|
182
182
|
name: "widget:create",
|
|
183
183
|
schema: widgetSchema,
|
|
184
184
|
access: { roles: ["Admin"] },
|
|
185
|
-
perform:
|
|
185
|
+
perform: stepsPipeline<z.infer<typeof widgetSchema>, { id: string }>(({ event, r }) => [
|
|
186
186
|
r.step.aggregate.create("widget", {
|
|
187
187
|
executor: widgetExecutor,
|
|
188
188
|
data: () => ({ label: event.payload.label }),
|
|
@@ -202,7 +202,7 @@ const annotateHandler = defineWriteHandler({
|
|
|
202
202
|
name: "widget:annotate",
|
|
203
203
|
schema: annotateSchema,
|
|
204
204
|
access: { roles: ["Admin"] },
|
|
205
|
-
perform:
|
|
205
|
+
perform: stepsPipeline<z.infer<typeof annotateSchema>, { id: string }>(({ event, r }) => [
|
|
206
206
|
r.step.aggregate.create("widget", {
|
|
207
207
|
executor: widgetExecutor,
|
|
208
208
|
data: () => ({ label: event.payload.label }),
|
|
@@ -230,7 +230,7 @@ const widgetUpdateHandler = defineWriteHandler({
|
|
|
230
230
|
name: "widget:update",
|
|
231
231
|
schema: widgetUpdateSchema,
|
|
232
232
|
access: { roles: ["Admin"] },
|
|
233
|
-
perform:
|
|
233
|
+
perform: stepsPipeline<z.infer<typeof widgetUpdateSchema>, { id: string }>(({ event, r }) => [
|
|
234
234
|
r.step.aggregate.update("widget", {
|
|
235
235
|
executor: widgetExecutor,
|
|
236
236
|
id: () => event.payload.id,
|
|
@@ -254,7 +254,7 @@ const widgetBrokenHandler = defineWriteHandler({
|
|
|
254
254
|
name: "widget:create-broken",
|
|
255
255
|
schema: z.object({}),
|
|
256
256
|
access: { roles: ["Admin"] },
|
|
257
|
-
perform:
|
|
257
|
+
perform: stepsPipeline<Record<string, never>, { id: string }>(({ r }) => [
|
|
258
258
|
r.step.aggregate.create("widget", {
|
|
259
259
|
executor: widgetExecutor,
|
|
260
260
|
// Intentionally omits the `label` field that the entity declares
|
|
@@ -278,7 +278,7 @@ const widgetCreateThenThrowHandler = defineWriteHandler({
|
|
|
278
278
|
name: "widget:create-then-throw",
|
|
279
279
|
schema: z.object({ label: z.string() }),
|
|
280
280
|
access: { roles: ["Admin"] },
|
|
281
|
-
perform:
|
|
281
|
+
perform: stepsPipeline<{ label: string }, never>(({ event, r }) => [
|
|
282
282
|
r.step.aggregate.create("widget", {
|
|
283
283
|
executor: widgetExecutor,
|
|
284
284
|
data: () => ({ label: event.payload.label }),
|
|
@@ -303,7 +303,7 @@ const forEachThenThrowHandler = defineWriteHandler({
|
|
|
303
303
|
name: "widget:foreach-then-throw",
|
|
304
304
|
schema: forEachThenThrowSchema,
|
|
305
305
|
access: { roles: ["Admin"] },
|
|
306
|
-
perform:
|
|
306
|
+
perform: stepsPipeline<{ labels: string[] }, never>(({ event, r }) => [
|
|
307
307
|
r.step.forEach({
|
|
308
308
|
over: () => event.payload.labels,
|
|
309
309
|
as: "label",
|
|
@@ -337,7 +337,7 @@ const lookupHandler = defineWriteHandler({
|
|
|
337
337
|
name: "widget:lookup",
|
|
338
338
|
schema: lookupSchema,
|
|
339
339
|
access: { roles: ["Admin"] },
|
|
340
|
-
perform:
|
|
340
|
+
perform: stepsPipeline<z.infer<typeof lookupSchema>, { found: boolean; label: string | null }>(
|
|
341
341
|
({ event, r }) => [
|
|
342
342
|
r.step.read.findOne("widget", {
|
|
343
343
|
table: widgetTable,
|
|
@@ -358,7 +358,7 @@ const listAllHandler = defineWriteHandler({
|
|
|
358
358
|
name: "widget:list",
|
|
359
359
|
schema: z.object({}),
|
|
360
360
|
access: { roles: ["Admin"] },
|
|
361
|
-
perform:
|
|
361
|
+
perform: stepsPipeline<Record<string, never>, { count: number }>(({ r }) => [
|
|
362
362
|
r.step.read.findMany("widgets", { table: widgetTable }),
|
|
363
363
|
r.step.return(({ steps }) => ({
|
|
364
364
|
isSuccess: true as const,
|
|
@@ -374,7 +374,7 @@ const listLimitedHandler = defineWriteHandler({
|
|
|
374
374
|
name: "widget:list-one",
|
|
375
375
|
schema: z.object({}),
|
|
376
376
|
access: { roles: ["Admin"] },
|
|
377
|
-
perform:
|
|
377
|
+
perform: stepsPipeline<Record<string, never>, { count: number }>(({ r }) => [
|
|
378
378
|
r.step.read.findMany("widgets", { table: widgetTable, limit: 1 }),
|
|
379
379
|
r.step.return(({ steps }) => ({
|
|
380
380
|
isSuccess: true as const,
|
|
@@ -388,7 +388,7 @@ const purgeLogHandler = defineWriteHandler({
|
|
|
388
388
|
name: "log:purge",
|
|
389
389
|
schema: purgeLogSchema,
|
|
390
390
|
access: { roles: ["Admin"] },
|
|
391
|
-
perform:
|
|
391
|
+
perform: stepsPipeline<z.infer<typeof purgeLogSchema>, { ok: true }>(({ event, r }) => [
|
|
392
392
|
r.step.unsafeProjectionDelete({
|
|
393
393
|
table: pipelineDemoLogTable,
|
|
394
394
|
where: () => ({ correlationId: event.payload.correlationId }),
|
|
@@ -403,7 +403,7 @@ const conditionalLogHandler = defineWriteHandler({
|
|
|
403
403
|
name: "log:conditional",
|
|
404
404
|
schema: conditionalLogSchema,
|
|
405
405
|
access: { roles: ["Admin"] },
|
|
406
|
-
perform:
|
|
406
|
+
perform: stepsPipeline<z.infer<typeof conditionalLogSchema>, { ok: true }>(({ event, r }) => [
|
|
407
407
|
r.step.branch({
|
|
408
408
|
if: () => event.payload.message.length > 0,
|
|
409
409
|
onTrue: [
|
|
@@ -428,7 +428,7 @@ const bulkLogHandler = defineWriteHandler({
|
|
|
428
428
|
name: "log:bulk",
|
|
429
429
|
schema: bulkLogSchema,
|
|
430
430
|
access: { roles: ["Admin"] },
|
|
431
|
-
perform:
|
|
431
|
+
perform: stepsPipeline<z.infer<typeof bulkLogSchema>, { count: number }>(({ event, r }) => [
|
|
432
432
|
r.step.forEach({
|
|
433
433
|
over: () => event.payload.correlationIds,
|
|
434
434
|
as: "correlationId",
|
|
@@ -476,7 +476,7 @@ const demoPipelineFeature = defineFeature("demoPipeline", (r) => {
|
|
|
476
476
|
let stack: TestStack;
|
|
477
477
|
const admin = TestUsers.admin;
|
|
478
478
|
|
|
479
|
-
describe("defineWriteHandler({ perform:
|
|
479
|
+
describe("defineWriteHandler({ perform: stepsPipeline(...) }) — real dispatcher path", () => {
|
|
480
480
|
beforeAll(async () => {
|
|
481
481
|
stack = await setupTestStack({ features: [demoPipelineFeature] });
|
|
482
482
|
// Push the read-side-projection table — not registered as an entity,
|
|
@@ -20,7 +20,7 @@ import { createRecordingProvider, type RecordingProvider } from "../../testing";
|
|
|
20
20
|
import { defineFeature } from "../define-feature";
|
|
21
21
|
import { defineWriteHandler } from "../define-handler";
|
|
22
22
|
import { createEntity, createTextField } from "../factories";
|
|
23
|
-
import {
|
|
23
|
+
import { stepsPipeline } from "../pipeline";
|
|
24
24
|
|
|
25
25
|
// Handler whose step bodies emit a span + a metric. The recording
|
|
26
26
|
// provider afterwards lets us assert both landed.
|
|
@@ -30,7 +30,7 @@ const observedHandler = defineWriteHandler({
|
|
|
30
30
|
name: "observed",
|
|
31
31
|
schema: observedSchema,
|
|
32
32
|
access: { roles: ["Admin"] },
|
|
33
|
-
perform:
|
|
33
|
+
perform: stepsPipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
34
34
|
r.step.compute("traced", (ctx) => {
|
|
35
35
|
// Span emitted from inside a step. End it explicitly because
|
|
36
36
|
// step.run is sync-or-async-but-not-otel-instrumented; the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Pipeline-engine performance smoke-test.
|
|
2
2
|
//
|
|
3
|
-
// Compares the M.1 pipeline-form handler ({ perform:
|
|
3
|
+
// Compares the M.1 pipeline-form handler ({ perform: stepsPipeline(...) })
|
|
4
4
|
// against the equivalent free-form handler ({ handler: async (...) })
|
|
5
5
|
// over N identical writes against the real Postgres stack. The point
|
|
6
6
|
// is NOT to optimise — the pipeline-form is a thin wrapper, not a
|
|
@@ -28,7 +28,7 @@ import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } fr
|
|
|
28
28
|
import { defineFeature } from "../define-feature";
|
|
29
29
|
import { defineWriteHandler } from "../define-handler";
|
|
30
30
|
import { createEntity, createNumberField, createTextField } from "../factories";
|
|
31
|
-
import {
|
|
31
|
+
import { stepsPipeline } from "../pipeline";
|
|
32
32
|
|
|
33
33
|
// Same logical operation in both handler-forms: read input, return a
|
|
34
34
|
// trivial transformed payload. No DB-write — the goal is to compare
|
|
@@ -41,7 +41,7 @@ const trivialPipeline = defineWriteHandler({
|
|
|
41
41
|
name: "trivial:pipeline",
|
|
42
42
|
schema: trivialSchema,
|
|
43
43
|
access: { roles: ["Admin"] },
|
|
44
|
-
perform:
|
|
44
|
+
perform: stepsPipeline<{ n: number }, { doubled: number }>(({ event, r }) => [
|
|
45
45
|
r.step.return(() => ({
|
|
46
46
|
isSuccess: true as const,
|
|
47
47
|
data: { doubled: event.payload.n * 2 },
|
|
@@ -9,7 +9,7 @@ import { describe, expect, it } from "bun:test";
|
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { TestUsers } from "../../stack";
|
|
11
11
|
import { defineWriteHandler } from "../define-handler";
|
|
12
|
-
import {
|
|
12
|
+
import { stepsPipeline } from "../pipeline";
|
|
13
13
|
import { buildBranchStep } from "../steps/branch";
|
|
14
14
|
import { buildForEachStep } from "../steps/for-each";
|
|
15
15
|
import { buildReturnStep } from "../steps/return";
|
|
@@ -21,7 +21,7 @@ describe("r.step.branch", () => {
|
|
|
21
21
|
name: "demo:branch-then",
|
|
22
22
|
schema: z.object({}),
|
|
23
23
|
access: { roles: ["User"] },
|
|
24
|
-
perform:
|
|
24
|
+
perform: stepsPipeline<Record<string, never>, { value: number }>(({ r }) => [
|
|
25
25
|
r.step.branch({
|
|
26
26
|
if: () => true,
|
|
27
27
|
onTrue: [r.step.compute("inThen", () => 42)],
|
|
@@ -47,7 +47,7 @@ describe("r.step.branch", () => {
|
|
|
47
47
|
name: "demo:branch-else",
|
|
48
48
|
schema: z.object({}),
|
|
49
49
|
access: { roles: ["User"] },
|
|
50
|
-
perform:
|
|
50
|
+
perform: stepsPipeline<Record<string, never>, { value: number }>(({ r }) => [
|
|
51
51
|
r.step.branch({
|
|
52
52
|
if: () => false,
|
|
53
53
|
onTrue: [r.step.compute("inThen", () => 42)],
|
|
@@ -72,7 +72,7 @@ describe("r.step.branch", () => {
|
|
|
72
72
|
name: "demo:branch-noop",
|
|
73
73
|
schema: z.object({}),
|
|
74
74
|
access: { roles: ["User"] },
|
|
75
|
-
perform:
|
|
75
|
+
perform: stepsPipeline<Record<string, never>, { ran: boolean }>(({ r }) => [
|
|
76
76
|
r.step.branch({
|
|
77
77
|
if: () => false,
|
|
78
78
|
onTrue: [r.step.compute("ran", () => true)],
|
|
@@ -122,7 +122,7 @@ describe("r.step.forEach", () => {
|
|
|
122
122
|
name: "demo:foreach",
|
|
123
123
|
schema: z.object({}),
|
|
124
124
|
access: { roles: ["User"] },
|
|
125
|
-
perform:
|
|
125
|
+
perform: stepsPipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
126
126
|
r.step.forEach({
|
|
127
127
|
over: () => [10, 20, 30],
|
|
128
128
|
as: "n",
|
|
@@ -153,7 +153,7 @@ describe("r.step.forEach", () => {
|
|
|
153
153
|
name: "demo:foreach-cleanup",
|
|
154
154
|
schema: z.object({}),
|
|
155
155
|
access: { roles: ["User"] },
|
|
156
|
-
perform:
|
|
156
|
+
perform: stepsPipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
157
157
|
r.step.forEach({
|
|
158
158
|
over: () => [1],
|
|
159
159
|
as: "tmp",
|
|
@@ -180,7 +180,7 @@ describe("r.step.forEach", () => {
|
|
|
180
180
|
name: "demo:foreach-empty",
|
|
181
181
|
schema: z.object({}),
|
|
182
182
|
access: { roles: ["User"] },
|
|
183
|
-
perform:
|
|
183
|
+
perform: stepsPipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
184
184
|
r.step.forEach({
|
|
185
185
|
over: () => [],
|
|
186
186
|
as: "n",
|
|
@@ -233,7 +233,7 @@ describe("r.step.forEach", () => {
|
|
|
233
233
|
name: "demo:foreach-throws",
|
|
234
234
|
schema: z.object({}),
|
|
235
235
|
access: { roles: ["User"] },
|
|
236
|
-
perform:
|
|
236
|
+
perform: stepsPipeline<Record<string, never>, never>(({ r }) => [
|
|
237
237
|
r.step.forEach({
|
|
238
238
|
over: () => [1, 2, 3],
|
|
239
239
|
as: "item",
|
|
@@ -266,7 +266,7 @@ describe("r.step.forEach", () => {
|
|
|
266
266
|
name: "demo:foreach-bad-over",
|
|
267
267
|
schema: z.object({}),
|
|
268
268
|
access: { roles: ["User"] },
|
|
269
|
-
perform:
|
|
269
|
+
perform: stepsPipeline<Record<string, never>, { ok: true }>(({ r }) => [
|
|
270
270
|
r.step.forEach({
|
|
271
271
|
// Cast: TypeScript would normally catch this; the test exercises
|
|
272
272
|
// the runtime guard for hand-crafted / dynamically built cases.
|
|
@@ -10,7 +10,7 @@ const stubContext = {} as unknown as AppContext;
|
|
|
10
10
|
// postQuery-Hook (F1) — feuert nach Query-Handler-Execute, vor Field-Access-
|
|
11
11
|
// Read-Filter. Zwei Registrierungs-Pfade:
|
|
12
12
|
// - r.hook("postQuery", "ns:query:list", fn) — handler-keyed
|
|
13
|
-
// - r.
|
|
13
|
+
// - r.hook("postQuery", { allOf: "thing" }, fn) — entity-keyed (alle Queries)
|
|
14
14
|
//
|
|
15
15
|
// Diese Tests pinnen die Invarianten:
|
|
16
16
|
// 1. Beide Registrierungs-Pfade landen in unabhängigen Maps
|
|
@@ -35,10 +35,10 @@ describe("postQuery hook registration", () => {
|
|
|
35
35
|
expect(entry?.[0]?.featureName).toBe("test");
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
test("r.
|
|
38
|
+
test("r.hook('postQuery', { allOf: entity }, fn) lands in entity-keyed entityHooks map", () => {
|
|
39
39
|
const feature = defineFeature("test", (r) => {
|
|
40
40
|
const thing = r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
41
|
-
r.
|
|
41
|
+
r.hook("postQuery", { allOf: thing }, noop);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
const entry = feature.entityHooks?.postQuery?.["thing"];
|
|
@@ -52,8 +52,8 @@ describe("postQuery hook registration", () => {
|
|
|
52
52
|
|
|
53
53
|
const feature = defineFeature("test", (r) => {
|
|
54
54
|
const thing = r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
55
|
-
r.
|
|
56
|
-
r.
|
|
55
|
+
r.hook("postQuery", { allOf: thing }, hookA);
|
|
56
|
+
r.hook("postQuery", { allOf: thing }, hookB);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
expect(feature.entityHooks?.postQuery?.["thing"]).toHaveLength(2);
|
|
@@ -81,7 +81,7 @@ describe("Registry getters", () => {
|
|
|
81
81
|
|
|
82
82
|
const feature = defineFeature("test", (r) => {
|
|
83
83
|
const thing = r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
84
|
-
r.
|
|
84
|
+
r.hook("postQuery", { allOf: thing }, fn);
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
const registry = createRegistry([feature]);
|
|
@@ -109,7 +109,7 @@ describe("Hook function semantics", () => {
|
|
|
109
109
|
|
|
110
110
|
const feature = defineFeature("test", (r) => {
|
|
111
111
|
const thing = r.entity("thing", createEntity({ table: "things", fields: {} }));
|
|
112
|
-
r.
|
|
112
|
+
r.hook("postQuery", { allOf: thing }, enrich);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
const registry = createRegistry([feature]);
|