@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,81 +1,19 @@
|
|
|
1
1
|
import type { ZodType, z } from "zod";
|
|
2
2
|
import type { ContainsSecret } from "../secrets/types";
|
|
3
3
|
import { runPipeline } from "./run-pipeline";
|
|
4
|
+
import type { HandlerContext, KumikoEventTypeMap, WriteEvent, WriteResult } from "./types";
|
|
4
5
|
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
RateLimitOption,
|
|
10
|
-
WriteEvent,
|
|
11
|
-
WriteResult,
|
|
12
|
-
} from "./types";
|
|
13
|
-
import type { PipelineDef } from "./types/step";
|
|
6
|
+
QueryHandlerDefinition,
|
|
7
|
+
WriteHandlerDefinition,
|
|
8
|
+
WriteHandlerInput,
|
|
9
|
+
} from "./types/define-handler";
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// the augmentation is visible); generic-type-aliases substitute at the
|
|
22
|
-
// definition-site (framework's compile, where the augmentation isn't
|
|
23
|
-
// visible) and collapse `keyof TMap` to `never`. See the spike-findings
|
|
24
|
-
// memory for the empirical proof.
|
|
25
|
-
//
|
|
26
|
-
// Two authoring forms — `handler` (free-form) or `perform: pipeline(...)`
|
|
27
|
-
// (step-pipeline). A `perform` is compiled to a handler-function at
|
|
28
|
-
// definition time; the dispatcher only ever sees `handler`.
|
|
29
|
-
|
|
30
|
-
export type WriteHandlerDefinition<
|
|
31
|
-
TName extends string = string,
|
|
32
|
-
TSchema extends ZodType = ZodType,
|
|
33
|
-
TData = unknown,
|
|
34
|
-
TMap extends object = KumikoEventTypeMap,
|
|
35
|
-
> = {
|
|
36
|
-
readonly name: TName;
|
|
37
|
-
readonly schema: TSchema;
|
|
38
|
-
readonly access?: AccessRule;
|
|
39
|
-
readonly unsafeSkipTransitionGuard?: boolean;
|
|
40
|
-
readonly rateLimit?: RateLimitOption;
|
|
41
|
-
readonly handler: (
|
|
42
|
-
event: WriteEvent<z.infer<TSchema>>,
|
|
43
|
-
context: HandlerContext<TMap>,
|
|
44
|
-
) => Promise<WriteResult<TData>>;
|
|
45
|
-
// Preserved when the author wrote a `perform` block — the original
|
|
46
|
-
// PipelineDef. Designer/AI/AST tools read this when present; the
|
|
47
|
-
// dispatcher ignores it and just calls `handler`. Absent on free-form
|
|
48
|
-
// handlers.
|
|
49
|
-
readonly perform?: PipelineDef<z.infer<TSchema>, TData>;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Author-facing input — accepts either the free-form `handler` or the
|
|
53
|
-
// pipeline-form `perform`. defineWriteHandler narrows them to the
|
|
54
|
-
// canonical WriteHandlerDefinition shape.
|
|
55
|
-
export type WriteHandlerInput<
|
|
56
|
-
TName extends string = string,
|
|
57
|
-
TSchema extends ZodType = ZodType,
|
|
58
|
-
TData = unknown,
|
|
59
|
-
TMap extends object = KumikoEventTypeMap,
|
|
60
|
-
> = {
|
|
61
|
-
readonly name: TName;
|
|
62
|
-
readonly schema: TSchema;
|
|
63
|
-
readonly access?: AccessRule;
|
|
64
|
-
readonly unsafeSkipTransitionGuard?: boolean;
|
|
65
|
-
readonly rateLimit?: RateLimitOption;
|
|
66
|
-
} & (
|
|
67
|
-
| {
|
|
68
|
-
readonly handler: (
|
|
69
|
-
event: WriteEvent<z.infer<TSchema>>,
|
|
70
|
-
context: HandlerContext<TMap>,
|
|
71
|
-
) => Promise<WriteResult<TData>>;
|
|
72
|
-
readonly perform?: never;
|
|
73
|
-
}
|
|
74
|
-
| {
|
|
75
|
-
readonly perform: PipelineDef<z.infer<TSchema>, TData>;
|
|
76
|
-
readonly handler?: never;
|
|
77
|
-
}
|
|
78
|
-
);
|
|
11
|
+
export type {
|
|
12
|
+
QueryHandlerDefinition,
|
|
13
|
+
StreamHandlerDefinition,
|
|
14
|
+
WriteHandlerDefinition,
|
|
15
|
+
WriteHandlerInput,
|
|
16
|
+
} from "./types/define-handler";
|
|
79
17
|
|
|
80
18
|
export function defineWriteHandler<
|
|
81
19
|
const TName extends string,
|
|
@@ -90,7 +28,20 @@ export function defineWriteHandler<
|
|
|
90
28
|
// responses get `[]`, so existing call-sites are unaffected. Checking it in a
|
|
91
29
|
// parameter post-inference (not as a `TData extends …` constraint, which TS
|
|
92
30
|
// rejects as circular, TS2313) is what makes inference survive.
|
|
93
|
-
|
|
31
|
+
//
|
|
32
|
+
// Membership form `true extends ContainsSecret<TData>` (556/1), not
|
|
33
|
+
// `ContainsSecret<TData> extends true`: when TData is a union like
|
|
34
|
+
// `{ok:true} | {s:Secret<string>}`, the naked-type-parameter conditional in
|
|
35
|
+
// ContainsSecret DISTRIBUTES over the union, so the result is
|
|
36
|
+
// `false | true` = `boolean`, not the literal `true` — `ContainsSecret<
|
|
37
|
+
// TData> extends true` is then false (boolean isn't assignable to the
|
|
38
|
+
// literal true) and the old check silently fell through to `[]` even with
|
|
39
|
+
// a real leak in one branch. Putting the naked `true` on the LEFT instead
|
|
40
|
+
// keeps the check fail-closed for the union case (`true extends boolean`
|
|
41
|
+
// is true) without eagerly normalizing ContainsSecret<TData> against a
|
|
42
|
+
// literal — which is what blew up TS's instantiation depth on generic
|
|
43
|
+
// call-sites (createTokenRequestHandler's still-unresolved TSuccessKind).
|
|
44
|
+
..._noSecretInResponse: true extends ContainsSecret<TData>
|
|
94
45
|
? [
|
|
95
46
|
secretLeak: "A handler response must not contain a Secret<> — call .reveal() and return the plaintext, or drop the field.",
|
|
96
47
|
]
|
|
@@ -113,7 +64,7 @@ export function defineWriteHandler<
|
|
|
113
64
|
throw new Error(
|
|
114
65
|
`defineWriteHandler("${def.name}"): both \`handler\` and \`perform\` are set. ` +
|
|
115
66
|
`Pick one — \`handler\` for the free-form async function, ` +
|
|
116
|
-
`\`perform:
|
|
67
|
+
`\`perform: stepsPipeline(...)\` for the step-pipeline form. ` +
|
|
117
68
|
`(See step-vocabulary.md for which form fits.)`,
|
|
118
69
|
);
|
|
119
70
|
}
|
|
@@ -147,24 +98,6 @@ export function defineWriteHandler<
|
|
|
147
98
|
return { ...base, handler: def.handler };
|
|
148
99
|
}
|
|
149
100
|
|
|
150
|
-
// --- Query Handler Definition ---
|
|
151
|
-
|
|
152
|
-
export type QueryHandlerDefinition<
|
|
153
|
-
TName extends string = string,
|
|
154
|
-
TSchema extends ZodType = ZodType,
|
|
155
|
-
TResult = unknown,
|
|
156
|
-
TMap extends object = KumikoEventTypeMap,
|
|
157
|
-
> = {
|
|
158
|
-
readonly name: TName;
|
|
159
|
-
readonly schema: TSchema;
|
|
160
|
-
readonly access?: AccessRule;
|
|
161
|
-
readonly rateLimit?: RateLimitOption;
|
|
162
|
-
readonly handler: (
|
|
163
|
-
query: QueryEvent<z.infer<TSchema>>,
|
|
164
|
-
context: HandlerContext<TMap>,
|
|
165
|
-
) => Promise<TResult>;
|
|
166
|
-
};
|
|
167
|
-
|
|
168
101
|
export function defineQueryHandler<
|
|
169
102
|
const TName extends string,
|
|
170
103
|
TSchema extends ZodType,
|
|
@@ -174,7 +107,8 @@ export function defineQueryHandler<
|
|
|
174
107
|
def: QueryHandlerDefinition<TName, TSchema, TResult, TMap>,
|
|
175
108
|
// R6: phantom rest-param — see defineWriteHandler. Forbids a Secret<> in the
|
|
176
109
|
// inferred query response `TResult` at compile time; `[]` for clean responses.
|
|
177
|
-
|
|
110
|
+
// Membership form (556/1) — see defineWriteHandler's comment for why.
|
|
111
|
+
..._noSecretInResponse: true extends ContainsSecret<TResult>
|
|
178
112
|
? [
|
|
179
113
|
secretLeak: "A handler response must not contain a Secret<> — call .reveal() and return the plaintext, or drop the field.",
|
|
180
114
|
]
|
|
@@ -57,7 +57,7 @@ export type WorkflowInput<TPayload = unknown, TData = unknown> = {
|
|
|
57
57
|
* defineWorkflow({
|
|
58
58
|
* name: "user-onboarding",
|
|
59
59
|
* trigger: { kind: "event", eventType: "user.signed-up" },
|
|
60
|
-
* steps:
|
|
60
|
+
* steps: stepsPipeline(({ event, r }) => [
|
|
61
61
|
* r.step.mail.send({ to: () => event.payload.email, subject: "Welcome!", body: "..." }),
|
|
62
62
|
* r.step.wait({ for: "P1D" }),
|
|
63
63
|
* r.step.read.findOne("user", { table: userTable, where: ... }),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Registry } from "./types";
|
|
1
|
+
import type { FeatureDefinition, Registry } from "./types";
|
|
2
2
|
|
|
3
3
|
// Callback that returns the current global-toggle override for a feature.
|
|
4
4
|
// `true` = explicit global row says enabled.
|
|
@@ -11,6 +11,16 @@ import type { Registry } from "./types";
|
|
|
11
11
|
// a Map lookup to keep compute() allocation-light.
|
|
12
12
|
export type ToggleReader = (featureName: string) => boolean | undefined;
|
|
13
13
|
|
|
14
|
+
// A feature is toggleable iff it declared r.toggleable() at define-time
|
|
15
|
+
// (toggleableDefault set). Non-toggleable features are always-on and ignore
|
|
16
|
+
// overrides entirely — used here, by compose-tier-resolver, and by
|
|
17
|
+
// tier-engine's always-on set to agree on the same rule.
|
|
18
|
+
export function isToggleableFeature(
|
|
19
|
+
feature: FeatureDefinition,
|
|
20
|
+
): feature is FeatureDefinition & { toggleableDefault: boolean } {
|
|
21
|
+
return feature.toggleableDefault !== undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
// Compute the set of effectively-enabled features for the current call.
|
|
15
25
|
//
|
|
16
26
|
// Rules (AND-combined, any false wins):
|
|
@@ -35,7 +45,7 @@ export function computeEffectiveFeatures(
|
|
|
35
45
|
// Raw enablement, before cascade.
|
|
36
46
|
const raw = new Map<string, boolean>();
|
|
37
47
|
for (const feature of registry.features.values()) {
|
|
38
|
-
if (feature
|
|
48
|
+
if (!isToggleableFeature(feature)) {
|
|
39
49
|
raw.set(feature.name, true);
|
|
40
50
|
continue;
|
|
41
51
|
}
|
|
@@ -18,6 +18,21 @@ import type {
|
|
|
18
18
|
QueryHandlerDef,
|
|
19
19
|
WriteHandlerDef,
|
|
20
20
|
} from "./types";
|
|
21
|
+
import type {
|
|
22
|
+
EntityCrudRegistrar,
|
|
23
|
+
EntityCrudVerb,
|
|
24
|
+
EntityHandlerOptions,
|
|
25
|
+
EntityQueryHandlerOptions,
|
|
26
|
+
RegisterEntityCrudOptions,
|
|
27
|
+
} from "./types/entity-handlers";
|
|
28
|
+
|
|
29
|
+
export type {
|
|
30
|
+
EntityCrudRegistrar,
|
|
31
|
+
EntityCrudVerb,
|
|
32
|
+
EntityHandlerOptions,
|
|
33
|
+
EntityQueryHandlerOptions,
|
|
34
|
+
RegisterEntityCrudOptions,
|
|
35
|
+
} from "./types/entity-handlers";
|
|
21
36
|
|
|
22
37
|
// Convention-based handler factories for event-sourced aggregates.
|
|
23
38
|
//
|
|
@@ -27,6 +42,9 @@ import type {
|
|
|
27
42
|
//
|
|
28
43
|
// Two API shapes — pick one per project, don't mix:
|
|
29
44
|
//
|
|
45
|
+
// PREFERRED — full standard CRUD set in one call:
|
|
46
|
+
// r.crud("note", noteEntity, { write: { access }, read: { access } })
|
|
47
|
+
//
|
|
30
48
|
// PREFERRED — one function per verb, type-safe, no magic strings:
|
|
31
49
|
// r.writeHandler(defineEntityCreateHandler("note", noteEntity, { access }))
|
|
32
50
|
// r.writeHandler(defineEntityUpdateHandler("note", noteEntity, { access }))
|
|
@@ -173,7 +191,15 @@ export function defineEntityWriteHandler(
|
|
|
173
191
|
changes: buildUpdateSchema(entity),
|
|
174
192
|
});
|
|
175
193
|
handler = async (event, ctx) =>
|
|
176
|
-
|
|
194
|
+
// skipUnchanged (#464): API-driven updates diff against the stored
|
|
195
|
+
// row so a resubmitted-but-identical field doesn't force a fresh
|
|
196
|
+
// pii/encrypted ciphertext. Direct executor.update() callers (e.g.
|
|
197
|
+
// KEK-rotation, the user-data-rights #494 backfill) don't go through
|
|
198
|
+
// this handler and keep today's always-re-encrypt behavior, which
|
|
199
|
+
// they rely on to intentionally force a fresh event/ciphertext.
|
|
200
|
+
executor.update(event.payload as UpdatePayload, event.user, ctx.db, {
|
|
201
|
+
skipUnchanged: true,
|
|
202
|
+
}); // @cast-boundary engine-payload
|
|
177
203
|
break;
|
|
178
204
|
case "delete":
|
|
179
205
|
schema = idSchema;
|
|
@@ -319,16 +345,6 @@ export function defineEntityQueryHandler(
|
|
|
319
345
|
// would need a Branded-EntityDefinition with `softDelete: true` literal
|
|
320
346
|
// — feasible but not yet wired; the runtime guard catches misuse.
|
|
321
347
|
|
|
322
|
-
type EntityHandlerOptions = { readonly access?: AccessRule };
|
|
323
|
-
type EntityQueryHandlerOptions = EntityHandlerOptions & {
|
|
324
|
-
/** Reads across every tenant instead of the caller's own — for a
|
|
325
|
-
* SystemAdmin-only operator inspector over an otherwise tenant-scoped
|
|
326
|
-
* entity. Scope this to the ONE handler that needs it rather than making
|
|
327
|
-
* the whole feature r.systemScope(), which would drop tenant isolation
|
|
328
|
-
* from every other handler the feature registers too. */
|
|
329
|
-
readonly crossTenant?: boolean;
|
|
330
|
-
};
|
|
331
|
-
|
|
332
348
|
// @wrapper-known semantic-alias
|
|
333
349
|
export function defineEntityCreateHandler(
|
|
334
350
|
entityName: string,
|
|
@@ -434,3 +450,52 @@ export function defineProjectionQueryHandler(
|
|
|
434
450
|
...(options?.access && { access: options.access }),
|
|
435
451
|
};
|
|
436
452
|
}
|
|
453
|
+
|
|
454
|
+
function defaultCrudVerbs(entity: EntityDefinition): Record<EntityCrudVerb, boolean> {
|
|
455
|
+
return {
|
|
456
|
+
create: true,
|
|
457
|
+
update: true,
|
|
458
|
+
delete: true,
|
|
459
|
+
restore: entity.softDelete === true,
|
|
460
|
+
list: true,
|
|
461
|
+
detail: true,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Register standard entity CRUD handlers in one call. Access stays explicit — no openToAll default. */
|
|
466
|
+
export function registerEntityCrud(
|
|
467
|
+
r: EntityCrudRegistrar,
|
|
468
|
+
entityName: string,
|
|
469
|
+
entity: EntityDefinition,
|
|
470
|
+
options?: RegisterEntityCrudOptions,
|
|
471
|
+
): void {
|
|
472
|
+
const verbs = { ...defaultCrudVerbs(entity), ...options?.verbs };
|
|
473
|
+
if (verbs.restore && entity.softDelete !== true) {
|
|
474
|
+
throw new Error(`restore requested but entity "${entityName}" has no softDelete: true`);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (options?.registerEntity !== false) {
|
|
478
|
+
r.entity(entityName, entity);
|
|
479
|
+
}
|
|
480
|
+
const writeOpts = options?.write;
|
|
481
|
+
const readOpts = options?.read;
|
|
482
|
+
|
|
483
|
+
if (verbs.create) {
|
|
484
|
+
r.writeHandler(defineEntityCreateHandler(entityName, entity, writeOpts));
|
|
485
|
+
}
|
|
486
|
+
if (verbs.update) {
|
|
487
|
+
r.writeHandler(defineEntityUpdateHandler(entityName, entity, writeOpts));
|
|
488
|
+
}
|
|
489
|
+
if (verbs.delete) {
|
|
490
|
+
r.writeHandler(defineEntityDeleteHandler(entityName, entity, writeOpts));
|
|
491
|
+
}
|
|
492
|
+
if (verbs.restore) {
|
|
493
|
+
r.writeHandler(defineEntityRestoreHandler(entityName, entity, writeOpts));
|
|
494
|
+
}
|
|
495
|
+
if (verbs.list) {
|
|
496
|
+
r.queryHandler(defineEntityListHandler(entityName, entity, readOpts));
|
|
497
|
+
}
|
|
498
|
+
if (verbs.detail) {
|
|
499
|
+
r.queryHandler(defineEntityDetailHandler(entityName, entity, readOpts));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Hook-Signatur-Types für EXT_TENANT_DATA (DSGVO tenant-scoped destroy).
|
|
2
|
+
//
|
|
3
|
+
// Mirror of engine/extensions/user-data.ts at tenant granularity.
|
|
4
|
+
// tenant-lifecycle orchestrates destroy via registry.getExtensionUsages(EXT_TENANT_DATA).
|
|
5
|
+
|
|
6
|
+
import type { DbRunner } from "../../db/connection";
|
|
7
|
+
import type { Registry, TenantId } from "../types";
|
|
8
|
+
|
|
9
|
+
export interface TenantDataHookCtx {
|
|
10
|
+
readonly db: DbRunner;
|
|
11
|
+
readonly registry: Registry;
|
|
12
|
+
readonly tenantId: TenantId;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type TenantDataDestroyHook = (ctx: TenantDataHookCtx) => Promise<void>;
|
|
16
|
+
|
|
17
|
+
export interface TenantDataExtensionHooks {
|
|
18
|
+
readonly destroy: TenantDataDestroyHook;
|
|
19
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// Siehe docs/plans/datenschutz/user-data-rights.md.
|
|
15
15
|
|
|
16
16
|
import type { DbRunner } from "../../db/connection";
|
|
17
|
-
import type { TenantId } from "../types";
|
|
17
|
+
import type { Registry, TenantId } from "../types";
|
|
18
18
|
|
|
19
19
|
// SessionUser.id ist plattformweit `string` (kein Brand-Type). Wenn
|
|
20
20
|
// jemals ein UserId-Brand eingefuehrt wird, ersetzt man hier den
|
|
@@ -70,6 +70,17 @@ export interface UserDataStorageProvider {
|
|
|
70
70
|
|
|
71
71
|
export interface UserDataHookCtx {
|
|
72
72
|
readonly db: DbRunner;
|
|
73
|
+
/**
|
|
74
|
+
* The app registry. A forget hook that must erase CHILD read-model rows past
|
|
75
|
+
* the entity's own row — m:n join projections, per-parent detail projections —
|
|
76
|
+
* uses it to run those custom projections for the executor's
|
|
77
|
+
* `<entity>.forgotten` event: `runProjectionsForEvent(result.data.event,
|
|
78
|
+
* ctx.registry, ctx.db)`. `executor.forget` purges only its OWN projection,
|
|
79
|
+
* and the dispatcher's post-command projection pass does not fire in the forget
|
|
80
|
+
* pipeline (a job, not a dispatched command), so the hook must trigger the
|
|
81
|
+
* cascade itself. Live-erase + rebuild `<parent>.forgotten`-apply converge.
|
|
82
|
+
*/
|
|
83
|
+
readonly registry: Registry;
|
|
73
84
|
readonly tenantId: TenantId;
|
|
74
85
|
readonly userId: UserId;
|
|
75
86
|
/**
|
|
@@ -134,11 +145,27 @@ export type UserDataExportHook = (ctx: UserDataHookCtx) => Promise<UserDataExpor
|
|
|
134
145
|
*
|
|
135
146
|
* Idempotent — wenn der Job zweimal läuft (Crash-Recovery), darf der
|
|
136
147
|
* Hook nicht crashen.
|
|
148
|
+
*
|
|
149
|
+
* Return additive to `void`: a hook can return `{status:"ok"}` or
|
|
150
|
+
* `{status:"incomplete", reason}` to signal a partial success to the
|
|
151
|
+
* cleanup runner (e.g. an external provider call failed) without
|
|
152
|
+
* throwing/rolling back the sub-transaction. Existing `void` returners
|
|
153
|
+
* stay valid unchanged — including ones explicitly typed
|
|
154
|
+
* `Promise<void>` (an explicitly-annotated hook, unlike a contextually-
|
|
155
|
+
* typed arrow literal, needs `void` in the union itself: `void` is not
|
|
156
|
+
* assignable to `undefined`).
|
|
137
157
|
*/
|
|
158
|
+
// Intentional: void (not undefined) is required here so an explicitly-typed
|
|
159
|
+
// `Promise<void>` hook stays assignable (see UserDataDeleteHook's doc
|
|
160
|
+
// comment above).
|
|
161
|
+
export type UserDataDeleteHookResult =
|
|
162
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: see comment above
|
|
163
|
+
void | { readonly status: "ok" } | { readonly status: "incomplete"; readonly reason: string };
|
|
164
|
+
|
|
138
165
|
export type UserDataDeleteHook = (
|
|
139
166
|
ctx: UserDataHookCtx,
|
|
140
167
|
strategy: UserDataDeleteStrategy,
|
|
141
|
-
) => Promise<
|
|
168
|
+
) => Promise<UserDataDeleteHookResult>;
|
|
142
169
|
|
|
143
170
|
/**
|
|
144
171
|
* Komplette Hook-Tafel für EXT_USER_DATA. Sprint 2 user-data-rights
|
package/src/engine/factories.ts
CHANGED
|
@@ -133,6 +133,12 @@ export function createMultiSelectField<const TOptions extends readonly string[]>
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Numeric field — `double precision` column by default (fractional values
|
|
138
|
+
* allowed end to end). Pass `integer: true` for a 32-bit `integer` column
|
|
139
|
+
* with `.int()` write-boundary validation. Need exact decimal storage
|
|
140
|
+
* instead (money-adjacent math)? Use `createDecimalField` (`numeric`).
|
|
141
|
+
*/
|
|
136
142
|
export function createNumberField<R extends true | false = false>(
|
|
137
143
|
overrides?: Partial<Omit<NumberFieldDef, "type" | "required">> & { required?: R },
|
|
138
144
|
): NumberFieldDef & { required: R } {
|
|
@@ -230,8 +236,8 @@ export function createDateField<R extends true | false = false>(
|
|
|
230
236
|
* (createdAt, loginAt, actualPickupAt). Temporal.Instant intern.
|
|
231
237
|
*
|
|
232
238
|
* Mit `locatedBy: "<name>Tz"` wird das Feld zum Wall-Clock-Pair eines
|
|
233
|
-
* Termins an einem Ort — bevorzuge dafür
|
|
234
|
-
*
|
|
239
|
+
* Termins an einem Ort — bevorzuge dafür `createLocatedTimestampField()`,
|
|
240
|
+
* das EIN atomares Feld statt eines lose verdrahteten Pairs erzeugt.
|
|
235
241
|
*/
|
|
236
242
|
export function createTimestampField<R extends true | false = false>(
|
|
237
243
|
overrides?: Partial<Omit<TimestampFieldDef, "type" | "required">> & { required?: R },
|
|
@@ -300,53 +306,6 @@ export function createLocatedTimestampField<R extends true | false = false>(
|
|
|
300
306
|
} as LocatedTimestampFieldDef & { required: R }; // @cast-boundary engine-payload
|
|
301
307
|
}
|
|
302
308
|
|
|
303
|
-
/**
|
|
304
|
-
* @deprecated Verwende stattdessen `createLocatedTimestampField()` —
|
|
305
|
-
* EIN Field-Type statt zwei lose Pair-Felder. Migration: ersetze
|
|
306
|
-
* `...locatedTimestamp("pickup")` durch `pickup: createLocatedTimestampField()`.
|
|
307
|
-
*
|
|
308
|
-
* Wall-Clock-Termin an einem Ort — Helper der ZWEI verbundene Felder
|
|
309
|
-
* erzeugt. Bevorzugte Form für jeden Date-Wert mit Location-Bezug
|
|
310
|
-
* (Pickup, Delivery, Meeting, Schedule).
|
|
311
|
-
*
|
|
312
|
-
* ```ts
|
|
313
|
-
* r.entity("order", {
|
|
314
|
-
* fields: {
|
|
315
|
-
* ...locatedTimestamp("pickup"), // → pickupAt + pickupTz
|
|
316
|
-
* ...locatedTimestamp("delivery"), // → deliveryAt + deliveryTz
|
|
317
|
-
* },
|
|
318
|
-
* });
|
|
319
|
-
* ```
|
|
320
|
-
*
|
|
321
|
-
* Zur Laufzeit wird:
|
|
322
|
-
* - DB: `<name>_at TIMESTAMPTZ` (UTC) + `<name>_tz TEXT` (IANA-Name)
|
|
323
|
-
* - JSON: `{ <name>At: "2026-04-03T10:00:00", <name>Tz: "Europe/Lisbon" }`
|
|
324
|
-
* (Wall-Clock OHNE Offset, plus IANA-Name — zwei getrennte Felder)
|
|
325
|
-
* - Reducer/Apply: kann mit Temporal.ZonedDateTime arbeiten
|
|
326
|
-
*
|
|
327
|
-
* Optionen pro Feld (z.B. `required` auf den At-Teil) per zweitem Argument.
|
|
328
|
-
*/
|
|
329
|
-
export function locatedTimestamp(
|
|
330
|
-
name: string,
|
|
331
|
-
overrides?: { readonly required?: boolean; readonly access?: TimestampFieldDef["access"] },
|
|
332
|
-
): Readonly<Record<string, TimestampFieldDef | TzFieldDef>> {
|
|
333
|
-
const atName = `${name}At`;
|
|
334
|
-
const tzName = `${name}Tz`;
|
|
335
|
-
return {
|
|
336
|
-
[atName]: {
|
|
337
|
-
type: "timestamp",
|
|
338
|
-
locatedBy: tzName,
|
|
339
|
-
required: overrides?.required,
|
|
340
|
-
access: overrides?.access,
|
|
341
|
-
},
|
|
342
|
-
[tzName]: {
|
|
343
|
-
type: "tz",
|
|
344
|
-
required: overrides?.required,
|
|
345
|
-
access: overrides?.access,
|
|
346
|
-
},
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
309
|
export function createFileField(overrides?: Partial<Omit<FileFieldDef, "type">>): FileFieldDef {
|
|
351
310
|
return { type: "file", ...overrides };
|
|
352
311
|
}
|
|
@@ -56,7 +56,10 @@ defineFeature("todoList", (r) => {
|
|
|
56
56
|
r.defineEvent({
|
|
57
57
|
name: "taskCompleted",
|
|
58
58
|
schema: z.object({ id: z.string() }),
|
|
59
|
-
version:
|
|
59
|
+
version: 2,
|
|
60
|
+
migrations: {
|
|
61
|
+
"1": (old) => ({ ...old, done: true }),
|
|
62
|
+
},
|
|
60
63
|
});
|
|
61
64
|
|
|
62
65
|
r.writeHandler({
|
|
@@ -75,6 +78,15 @@ defineFeature("todoList", (r) => {
|
|
|
75
78
|
access: { openToAll: true },
|
|
76
79
|
});
|
|
77
80
|
|
|
81
|
+
r.streamHandler({
|
|
82
|
+
name: "task:stream",
|
|
83
|
+
schema: z.object({ prompt: z.string() }),
|
|
84
|
+
handler: async function* (input, ctx) {
|
|
85
|
+
yield "token";
|
|
86
|
+
},
|
|
87
|
+
access: { roles: ["user"] },
|
|
88
|
+
});
|
|
89
|
+
|
|
78
90
|
r.hook({
|
|
79
91
|
type: "postSave",
|
|
80
92
|
target: "task",
|
|
@@ -83,9 +95,9 @@ defineFeature("todoList", (r) => {
|
|
|
83
95
|
},
|
|
84
96
|
});
|
|
85
97
|
|
|
86
|
-
r.
|
|
98
|
+
r.hook({
|
|
87
99
|
type: "postDelete",
|
|
88
|
-
|
|
100
|
+
target: { allOf: "task" },
|
|
89
101
|
handler: async (event, ctx) => {
|
|
90
102
|
console.log("task deleted");
|
|
91
103
|
},
|
|
@@ -127,13 +139,6 @@ defineFeature("todoList", (r) => {
|
|
|
127
139
|
|
|
128
140
|
r.useExtension({ name: "auditLog", entity: "task" });
|
|
129
141
|
|
|
130
|
-
r.eventMigration({
|
|
131
|
-
event: "taskCompleted",
|
|
132
|
-
fromVersion: 1,
|
|
133
|
-
toVersion: 2,
|
|
134
|
-
transform: (old) => ({ ...old, done: true }),
|
|
135
|
-
});
|
|
136
|
-
|
|
137
142
|
r.job({
|
|
138
143
|
name: "cleanupExpired",
|
|
139
144
|
schedule: { cron: "0 3 * * *" },
|
|
@@ -199,7 +204,7 @@ describe("Canonical Object-Form — parser akzeptiert + extrahiert", () => {
|
|
|
199
204
|
expect(unknowns).toEqual([]);
|
|
200
205
|
});
|
|
201
206
|
|
|
202
|
-
test("alle
|
|
207
|
+
test("alle Pattern-Kinds sind erfasst", () => {
|
|
203
208
|
const kinds: ReadonlySet<string> = new Set(result.patterns.map((p) => p.kind));
|
|
204
209
|
const expected = [
|
|
205
210
|
// Static
|
|
@@ -223,8 +228,8 @@ describe("Canonical Object-Form — parser akzeptiert + extrahiert", () => {
|
|
|
223
228
|
"screen",
|
|
224
229
|
"writeHandler",
|
|
225
230
|
"queryHandler",
|
|
231
|
+
"streamHandler",
|
|
226
232
|
"hook",
|
|
227
|
-
"entityHook",
|
|
228
233
|
"job",
|
|
229
234
|
"notification",
|
|
230
235
|
"authClaims",
|
|
@@ -232,7 +237,6 @@ describe("Canonical Object-Form — parser akzeptiert + extrahiert", () => {
|
|
|
232
237
|
"projection",
|
|
233
238
|
"multiStreamProjection",
|
|
234
239
|
"defineEvent",
|
|
235
|
-
"eventMigration",
|
|
236
240
|
];
|
|
237
241
|
for (const e of expected) {
|
|
238
242
|
expect(kinds.has(e), `expected kind "${e}"`).toBe(true);
|
|
@@ -265,12 +269,14 @@ describe("Canonical Object-Form — parser akzeptiert + extrahiert", () => {
|
|
|
265
269
|
});
|
|
266
270
|
});
|
|
267
271
|
|
|
268
|
-
test("
|
|
269
|
-
const entityHook = result.patterns.find(
|
|
272
|
+
test("hook Pattern: entity-wide { allOf } target aus Object-Form", () => {
|
|
273
|
+
const entityHook = result.patterns.find(
|
|
274
|
+
(p) => p.kind === "hook" && p.hookType === "postDelete",
|
|
275
|
+
);
|
|
270
276
|
expect(entityHook).toMatchObject({
|
|
271
|
-
kind: "
|
|
277
|
+
kind: "hook",
|
|
272
278
|
hookType: "postDelete",
|
|
273
|
-
|
|
279
|
+
target: { allOf: "task" },
|
|
274
280
|
});
|
|
275
281
|
});
|
|
276
282
|
|
|
@@ -283,12 +289,13 @@ describe("Canonical Object-Form — parser akzeptiert + extrahiert", () => {
|
|
|
283
289
|
expect(claim).toMatchObject({ kind: "claimKey", shortName: "teamId" });
|
|
284
290
|
});
|
|
285
291
|
|
|
286
|
-
test("defineEvent: name + version aus Object-Form", () => {
|
|
292
|
+
test("defineEvent: name + version + migrations aus Object-Form", () => {
|
|
287
293
|
const ev = result.patterns.find((p) => p.kind === "defineEvent");
|
|
288
294
|
expect(ev).toMatchObject({
|
|
289
295
|
kind: "defineEvent",
|
|
290
296
|
eventName: "taskCompleted",
|
|
291
|
-
version:
|
|
297
|
+
version: 2,
|
|
298
|
+
migrations: { "1": expect.anything() },
|
|
292
299
|
});
|
|
293
300
|
});
|
|
294
301
|
|
|
@@ -334,16 +341,6 @@ describe("Canonical Object-Form — parser akzeptiert + extrahiert", () => {
|
|
|
334
341
|
});
|
|
335
342
|
});
|
|
336
343
|
|
|
337
|
-
test("eventMigration: event + fromVersion + toVersion aus Object-Form", () => {
|
|
338
|
-
const em = result.patterns.find((p) => p.kind === "eventMigration");
|
|
339
|
-
expect(em).toMatchObject({
|
|
340
|
-
kind: "eventMigration",
|
|
341
|
-
eventName: "taskCompleted",
|
|
342
|
-
fromVersion: 1,
|
|
343
|
-
toVersion: 2,
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
344
|
test("job: name + handlerBody.raw enthält den Closure-Body", () => {
|
|
348
345
|
const job = result.patterns.find((p) => p.kind === "job");
|
|
349
346
|
expect(job).toMatchObject({ kind: "job", jobName: "cleanupExpired" });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { registerFooScreens } from "./screens";
|
|
2
|
+
|
|
3
|
+
// biome-ignore lint/suspicious/noExplicitAny: structural parser test fixture, never executed or type-checked at runtime
|
|
4
|
+
declare function defineFeature(name: string, setup: (r: any) => void): void;
|
|
5
|
+
|
|
6
|
+
defineFeature("cross-file-registrar", (r) => {
|
|
7
|
+
r.requires("config");
|
|
8
|
+
registerFooScreens(r);
|
|
9
|
+
});
|
|
@@ -71,7 +71,7 @@ defineFeature("todoList", (r) => {
|
|
|
71
71
|
console.log("task saved");
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
r.
|
|
74
|
+
r.hook("postDelete", { allOf: "task" }, async (event, ctx) => {
|
|
75
75
|
console.log("task deleted");
|
|
76
76
|
});
|
|
77
77
|
|
|
@@ -136,7 +136,6 @@ describe("parseSourceFile against a complete inline-form feature", () => {
|
|
|
136
136
|
"writeHandler",
|
|
137
137
|
"queryHandler",
|
|
138
138
|
"hook",
|
|
139
|
-
"entityHook",
|
|
140
139
|
"metric",
|
|
141
140
|
"secret",
|
|
142
141
|
"claimKey",
|