@cosmicdrift/kumiko-framework 0.386.0 → 0.387.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/LICENSE +21 -57
- package/README.md +2 -2
- package/agents/widgets.md +39 -39
- package/dist/api/api-constants.js +2 -2
- package/dist/api/auth-middleware.js +7 -7
- package/dist/api/auth-rate-limiters.js +7 -3
- package/dist/api/auth-routes-mfa.js +1 -1
- package/dist/api/auth-routes-session.js +1 -1
- package/dist/api/auth-routes-shared.js +7 -3
- package/dist/api/auth-routes-signup.js +1 -1
- package/dist/api/client-ip.js +3 -1
- package/dist/api/extra-route.js +1 -1
- package/dist/api/pii-leak-guard.js +4 -2
- package/dist/api/readiness.js +5 -5
- package/dist/api/redis-sse-broker.js +8 -6
- package/dist/api/routes.js +2 -2
- package/dist/api/server.js +34 -33
- package/dist/api/sse-broker.js +1 -1
- package/dist/api/sse-route.d.ts +13 -13
- package/dist/api/sse-route.js +15 -15
- package/dist/auth/index.js +4 -4
- package/dist/auth/roles.d.ts +4 -4
- package/dist/auth/roles.js +24 -24
- package/dist/compliance/duration-spec.js +18 -20
- package/dist/compliance/index.js +2 -3
- package/dist/compliance/override-schema.d.ts +4 -4
- package/dist/compliance/override-schema.js +19 -22
- package/dist/compliance/profiles.d.ts +26 -29
- package/dist/compliance/profiles.js +62 -69
- package/dist/compliance/sub-processors.d.ts +28 -30
- package/dist/compliance/sub-processors.js +17 -21
- package/dist/consumer-cli.js +4 -4
- package/dist/crypto/blind-index.js +18 -17
- package/dist/crypto/ciphertext-pattern.js +1 -1
- package/dist/crypto/event-pii.js +3 -3
- package/dist/crypto/kek-source.d.ts +1 -1
- package/dist/crypto/kek-source.js +3 -2
- package/dist/crypto/kms-adapter.d.ts +1 -1
- package/dist/crypto/kms-adapter.js +4 -6
- package/dist/crypto/kms-wiring.d.ts +1 -2
- package/dist/crypto/kms-wiring.js +2 -2
- package/dist/crypto/pg-kms-adapter.js +2 -6
- package/dist/crypto/pii-field-encryption.js +4 -4
- package/dist/crypto/subject-resolver.d.ts +1 -1
- package/dist/crypto/subject-resolver.js +2 -2
- package/dist/db/apply-entity-event.d.ts +6 -6
- package/dist/db/apply-entity-event.js +55 -55
- package/dist/db/blind-index-cleanup.js +7 -7
- package/dist/db/collect-table-metas.js +10 -11
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +12 -12
- package/dist/db/cursor.js +3 -4
- package/dist/db/dialect.d.ts +2 -2
- package/dist/db/dialect.js +2 -2
- package/dist/db/eagerload.d.ts +3 -3
- package/dist/db/eagerload.js +21 -12
- package/dist/db/entity-table-meta.d.ts +2 -2
- package/dist/db/entity-table-meta.js +4 -4
- package/dist/db/event-store-executor-context.d.ts +1 -1
- package/dist/db/event-store-executor-context.js +13 -13
- package/dist/db/event-store-executor-read.js +27 -28
- package/dist/db/event-store-executor-write.js +65 -70
- package/dist/db/located-timestamp.d.ts +11 -11
- package/dist/db/located-timestamp.js +29 -30
- package/dist/db/migrate-generator.d.ts +1 -1
- package/dist/db/migrate-generator.js +26 -19
- package/dist/db/migrate-runner.js +25 -25
- package/dist/db/money.d.ts +6 -6
- package/dist/db/money.js +29 -27
- package/dist/db/queries/backfill-pii.js +5 -5
- package/dist/db/queries/ddl.js +1 -1
- package/dist/db/queries/es-ops.d.ts +1 -1
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.js +3 -3
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/projection-rebuild.js +3 -3
- package/dist/db/queries/raw-sql.d.ts +2 -2
- package/dist/db/queries/raw-sql.js +2 -2
- package/dist/db/queries/shadow-swap.js +19 -19
- package/dist/db/query.d.ts +1 -1
- package/dist/db/query.js +6 -6
- package/dist/db/rebuild-marker.js +20 -21
- package/dist/db/replay-migration-sql.js +2 -2
- package/dist/db/schema-inspection.js +4 -5
- package/dist/db/table-builder.d.ts +1 -2
- package/dist/db/table-builder.js +82 -82
- package/dist/db/tenant-db.d.ts +2 -2
- package/dist/db/tenant-db.js +9 -2
- package/dist/derivatives/delete-stored-file.js +5 -2
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/engine/boot-validator/access-roles.js +4 -3
- package/dist/engine/boot-validator/api-ext.js +14 -14
- package/dist/engine/boot-validator/config-deps.js +7 -6
- package/dist/engine/boot-validator/detail-screens.js +3 -3
- package/dist/engine/boot-validator/entity-handler.js +60 -61
- package/dist/engine/boot-validator/gdpr-storage.js +4 -4
- package/dist/engine/boot-validator/i18n-keys.js +1 -1
- package/dist/engine/boot-validator/index.d.ts +2 -2
- package/dist/engine/boot-validator/index.js +20 -20
- package/dist/engine/boot-validator/nav.js +5 -5
- package/dist/engine/boot-validator/ownership.js +12 -9
- package/dist/engine/boot-validator/parent-ref.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +50 -57
- package/dist/engine/boot-validator/projection-list-screens.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +1 -1
- package/dist/engine/boot-validator/screens-actions.js +15 -16
- package/dist/engine/boot-validator/screens-forms.js +10 -11
- package/dist/engine/boot-validator/screens-lists.js +14 -15
- package/dist/engine/boot-validator/screens.js +34 -40
- package/dist/engine/boot-validator/security-baseline.js +3 -2
- package/dist/engine/boot-validator/transfer-graph.js +2 -2
- package/dist/engine/boot-validator/workspaces.js +3 -3
- package/dist/engine/bridge-stub.js +3 -4
- package/dist/engine/build-app-schema.js +75 -75
- package/dist/engine/build-config-feature-schema.js +50 -50
- package/dist/engine/build-target.js +20 -23
- package/dist/engine/codemod/pipeline-codemod.js +20 -13
- package/dist/engine/config-helpers.js +4 -4
- package/dist/engine/constants.js +2 -2
- package/dist/engine/create-app.js +2 -2
- package/dist/engine/define-feature.js +12 -11
- package/dist/engine/define-handler.js +12 -11
- package/dist/engine/define-step.js +1 -1
- package/dist/engine/define-workflow.d.ts +5 -5
- package/dist/engine/define-workflow.js +6 -6
- package/dist/engine/entity-handlers.js +13 -14
- package/dist/engine/event-def.js +2 -2
- package/dist/engine/extension-names.d.ts +16 -17
- package/dist/engine/extension-names.js +37 -38
- package/dist/engine/extensions/escape-hatch-usage.js +1 -1
- package/dist/engine/extensions/tenant-data.js +2 -2
- package/dist/engine/extensions/user-data.d.ts +23 -24
- package/dist/engine/factories.d.ts +14 -13
- package/dist/engine/factories.js +38 -37
- package/dist/engine/feature-ast/entity-field-types.js +1 -1
- package/dist/engine/feature-ast/extractors/handlers.js +1 -1
- package/dist/engine/feature-ast/extractors/hooks.js +1 -1
- package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
- package/dist/engine/feature-ast/extractors/shared.js +2 -3
- package/dist/engine/feature-ast/parse.js +13 -13
- package/dist/engine/feature-ast/patch.js +1 -1
- package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
- package/dist/engine/feature-ast/patterns.d.ts +2 -2
- package/dist/engine/feature-ast/render.js +2 -2
- package/dist/engine/feature-config-events-jobs.js +15 -12
- package/dist/engine/feature-entity-handlers.js +9 -5
- package/dist/engine/feature-manifest.d.ts +9 -9
- package/dist/engine/feature-manifest.js +6 -7
- package/dist/engine/feature-ui-extensions.d.ts +3 -3
- package/dist/engine/feature-ui-extensions.js +30 -22
- package/dist/engine/field-access.js +5 -6
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -4
- package/dist/engine/object-form.d.ts +1 -0
- package/dist/engine/object-form.js +14 -4
- package/dist/engine/ownership.js +3 -3
- package/dist/engine/parse-ref-target.js +5 -7
- package/dist/engine/pattern-library/index.js +2 -2
- package/dist/engine/pattern-library/library.js +2 -2
- package/dist/engine/pattern-library/types.js +3 -3
- package/dist/engine/pipeline.js +2 -2
- package/dist/engine/project-app-schema-for-roles.js +1 -1
- package/dist/engine/projection-helpers.js +9 -9
- package/dist/engine/qualified-name.js +2 -2
- package/dist/engine/registry-facade.js +1 -1
- package/dist/engine/registry-ingest.js +12 -12
- package/dist/engine/registry-state.js +17 -19
- package/dist/engine/registry-validate.js +12 -12
- package/dist/engine/run-in.js +2 -2
- package/dist/engine/run-pipeline.js +7 -0
- package/dist/engine/schema-builder.js +40 -38
- package/dist/engine/screen-filter-ops.js +17 -20
- package/dist/engine/screen-helpers.js +4 -3
- package/dist/engine/state-machine.d.ts +12 -12
- package/dist/engine/state-machine.js +7 -7
- package/dist/engine/steps/_resolver-utils.js +4 -3
- package/dist/engine/steps/for-each.js +4 -4
- package/dist/engine/steps/unsafe-projection-delete.js +1 -1
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
- package/dist/engine/steps/wait-for-event.js +4 -4
- package/dist/engine/steps/workflow-system-events.js +3 -3
- package/dist/engine/tier-resolver-extension.d.ts +29 -27
- package/dist/engine/tier-resolver-extension.js +26 -26
- package/dist/engine/transfer-adjacency.js +2 -2
- package/dist/engine/validate-projection-allowlist.js +7 -27
- package/dist/engine/walk-step-instances.d.ts +2 -0
- package/dist/engine/walk-step-instances.js +38 -0
- package/dist/engine/where-rule-lint.js +1 -1
- package/dist/engine/write-handler-qn-extract.js +1 -1
- package/dist/entrypoint/index.js +3 -3
- package/dist/env/index.d.ts +7 -6
- package/dist/env/index.js +10 -9
- package/dist/errors/transition-details.js +7 -7
- package/dist/errors/write-error-info.d.ts +6 -6
- package/dist/errors/write-error-info.js +6 -6
- package/dist/es-ops/context.d.ts +9 -9
- package/dist/es-ops/context.js +28 -28
- package/dist/es-ops/index.js +7 -9
- package/dist/es-ops/operations-schema.js +4 -10
- package/dist/es-ops/runner.d.ts +11 -11
- package/dist/es-ops/runner.js +49 -53
- package/dist/es-ops/types.d.ts +55 -55
- package/dist/es-ops/types.js +11 -11
- package/dist/event-store/admin-api.js +6 -7
- package/dist/event-store/errors.js +1 -1
- package/dist/event-store/event-store.js +2 -2
- package/dist/event-store/rebuild-dead-letter.js +1 -1
- package/dist/files/feature.js +9 -10
- package/dist/files/file-handle.js +5 -6
- package/dist/files/file-ref-entity.js +15 -15
- package/dist/files/file-ref-table.js +7 -8
- package/dist/files/file-routes.d.ts +7 -0
- package/dist/files/file-routes.js +11 -5
- package/dist/files/in-memory-provider.js +8 -9
- package/dist/files/local-provider.js +18 -25
- package/dist/files/provider-resolver.d.ts +7 -7
- package/dist/files/storage-tracking.js +2 -2
- package/dist/files/zip-stream.d.ts +12 -19
- package/dist/files/zip-stream.js +44 -63
- package/dist/i18n/required-surface-keys.d.ts +1 -1
- package/dist/i18n/required-surface-keys.js +8 -8
- package/dist/jobs/job-runner-helpers.js +8 -2
- package/dist/jobs/job-runner-schedulers.d.ts +1 -1
- package/dist/jobs/job-runner-schedulers.js +3 -4
- package/dist/jobs/job-runner.js +13 -15
- package/dist/lifecycle/lifecycle.js +3 -5
- package/dist/lifecycle/signal-handlers.js +5 -4
- package/dist/logging/utils.d.ts +2 -1
- package/dist/logging/utils.js +38 -10
- package/dist/migrations/index.js +1 -1
- package/dist/migrations/kumiko-drift.js +14 -13
- package/dist/migrations/pending-rebuilds.d.ts +21 -20
- package/dist/migrations/pending-rebuilds.js +27 -27
- package/dist/migrations/projection-table-index.d.ts +3 -3
- package/dist/migrations/projection-table-index.js +8 -8
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.js +3 -3
- package/dist/pipeline/append-event-core.js +11 -2
- package/dist/pipeline/cascade-handler.d.ts +1 -1
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +10 -10
- package/dist/pipeline/dispatch-shared.d.ts +1 -0
- package/dist/pipeline/dispatch-shared.js +15 -17
- package/dist/pipeline/dispatch-stream.js +1 -1
- package/dist/pipeline/dispatch-write.js +3 -3
- package/dist/pipeline/dispatcher-utils.d.ts +1 -1
- package/dist/pipeline/dispatcher-utils.js +1 -1
- package/dist/pipeline/event-consumer-state.js +2 -2
- package/dist/pipeline/event-dispatcher-delivery.js +1 -1
- package/dist/pipeline/event-dispatcher.js +17 -28
- package/dist/pipeline/irreversible-operation-gate.js +1 -1
- package/dist/pipeline/lifecycle-pipeline.js +9 -9
- package/dist/pipeline/member-read-only-transaction.js +1 -3
- package/dist/pipeline/msp-rebuild.js +1 -1
- package/dist/pipeline/projection-rebuild.js +13 -12
- package/dist/pipeline/system-hooks.js +39 -41
- package/dist/pipeline/system-identity-switch.js +1 -3
- package/dist/pipeline/tenant-timezone-cache.js +1 -1
- package/dist/random/generate.d.ts +29 -29
- package/dist/random/generate.js +30 -30
- package/dist/random/words.js +20 -20
- package/dist/rate-limit/middleware.js +9 -5
- package/dist/redis/client.js +2 -2
- package/dist/redis/pubsub-signal.js +14 -17
- package/dist/schema-cli.js +116 -22
- package/dist/search/index.js +4 -4
- package/dist/search/purge-subject.js +4 -5
- package/dist/search/reindex-entity.js +6 -9
- package/dist/time/boot-tz-warning.d.ts +2 -2
- package/dist/time/boot-tz-warning.js +8 -8
- package/dist/time/iana.js +10 -10
- package/dist/time/index.js +9 -9
- package/dist/time/legacy-date.d.ts +2 -2
- package/dist/time/legacy-date.js +6 -8
- package/dist/time/polyfill.js +1 -1
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +1 -1
- package/dist/ui-types/app-schema.js +7 -7
- package/dist/ui-types/list-row-meta.js +4 -4
- package/dist/upgrade-cli.js +10 -9
- package/dist/utils/compare.js +1 -1
- package/package.json +4 -4
- package/src/changes.json +48 -0
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
- package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
- package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
- package/src/scripts/codemod/pii-personal-migration.ts +17 -8
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
// (state-machine.assertTransition
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Shared detail shape for invalid_transition errors. Both paths
|
|
2
|
+
// (`state-machine.assertTransition` and `failTransition`) must provide
|
|
3
|
+
// identically structured details so HTTP clients can parse the 422 body uniformly:
|
|
4
|
+
// `from`/`to`/`allowed` are the structured fields, while `message` is the
|
|
5
|
+
// human-readable form.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// `validTargets` is not re-exported as a CSV string; callers that need CSV can use
|
|
8
|
+
// `allowed.join(", ")`. One shape defines the contract.
|
|
9
9
|
export function buildInvalidTransitionDetails(from, to, allowed) {
|
|
10
10
|
return {
|
|
11
11
|
from,
|
|
@@ -7,13 +7,13 @@ export declare function failUnprocessable(reason: string, details?: Readonly<Rec
|
|
|
7
7
|
readonly reason?: never;
|
|
8
8
|
}): WriteFailure;
|
|
9
9
|
/**
|
|
10
|
-
* Convenience
|
|
11
|
-
* reason="invalid_transition"
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* Convenience for state-transition rejects: produces a WriteFailure with
|
|
11
|
+
* reason="invalid_transition", a structured `from`/`to`/`allowed` detail
|
|
12
|
+
* block, and a readable message. This pattern recurs in publicstatus
|
|
13
|
+
* maintenance and existing state-machine helpers, so the helper collects it
|
|
14
|
+
* in one call instead of requiring three manual detail fields.
|
|
15
15
|
*
|
|
16
|
-
* `allowed`
|
|
16
|
+
* `allowed` is typically `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
|
|
17
17
|
*/
|
|
18
18
|
export declare function failTransition(from: string, to: string, allowed: readonly string[]): WriteFailure;
|
|
19
19
|
export declare function toWriteErrorInfo(err: KumikoError): WriteErrorInfo;
|
|
@@ -24,13 +24,13 @@ export function failUnprocessable(reason, details) {
|
|
|
24
24
|
return writeFailure(new UnprocessableError(reason, details ? { details } : undefined));
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Convenience
|
|
28
|
-
* reason="invalid_transition"
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Convenience for state-transition rejects: produces a WriteFailure with
|
|
28
|
+
* reason="invalid_transition", a structured `from`/`to`/`allowed` detail
|
|
29
|
+
* block, and a readable message. This pattern recurs in publicstatus
|
|
30
|
+
* maintenance and existing state-machine helpers, so the helper collects it
|
|
31
|
+
* in one call instead of requiring three manual detail fields.
|
|
32
32
|
*
|
|
33
|
-
* `allowed`
|
|
33
|
+
* `allowed` is typically `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
|
|
34
34
|
*/
|
|
35
35
|
export function failTransition(from, to, allowed) {
|
|
36
36
|
return writeFailure(new UnprocessableError(FrameworkReasons.invalidTransition, {
|
package/dist/es-ops/context.d.ts
CHANGED
|
@@ -5,15 +5,15 @@ export type CreateSeedMigrationContextArgs = {
|
|
|
5
5
|
readonly dispatcher: Dispatcher;
|
|
6
6
|
readonly dbRunner: DbRunner;
|
|
7
7
|
};
|
|
8
|
-
/** Builder
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
/** Builder that returns a factory function called by the runner for each
|
|
9
|
+
* migration. dbRunner may be a top-level connection or a transaction—read
|
|
10
|
+
* helpers use it directly, while systemWriteAs delegates to the dispatcher.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* runner.integration.test.ts
|
|
12
|
+
* dispatcher.write does not receive the runner transaction: runBatch opens
|
|
13
|
+
* a separate transaction on context.db (using a separate pool connection).
|
|
14
|
+
* Writes therefore commit independently: a runner failure rolls back only
|
|
15
|
+
* the marker insert and dbRunner reads; dispatcher writes remain committed.
|
|
16
|
+
* Seeds must therefore be idempotent (verified in
|
|
17
|
+
* runner.integration.test.ts “dispatcher-writes vor throw bleiben committed”). */
|
|
18
18
|
export declare function createSeedMigrationContext(args: CreateSeedMigrationContextArgs): SeedMigrationContext;
|
|
19
19
|
export type { SeedMigrationContext } from "./types.ts";
|
package/dist/es-ops/context.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
// SeedMigrationContext
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// SeedMigrationContext builder. The caller (runProdApp/CLI) supplies a
|
|
2
|
+
// preconfigured dispatcher; the builder creates a transaction-scoped context
|
|
3
|
+
// for each migration that uses the existing handler path through
|
|
4
|
+
// dispatcher.write—the same pattern as a user UI click.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// config-seed.ts:40). Events
|
|
8
|
-
//
|
|
6
|
+
// The system user bypasses access checks (the standard seed pattern; see
|
|
7
|
+
// config-seed.ts:40). Events have createdBy = SYSTEM_TENANT_ID user,
|
|
8
|
+
// making them auditable.
|
|
9
9
|
import { configuredPiiSubjectKms, decryptPiiFieldValues } from "../crypto/index.js";
|
|
10
10
|
import { selectAllTenants, selectMembershipsOfUser, selectTemplateResources, selectUserByEmail, } from "../db/queries/seed-context.js";
|
|
11
11
|
import { createSystemUser, SYSTEM_TENANT_ID } from "../engine/index.js";
|
|
12
12
|
import { parseStringArrayJson } from "../utils/parse-string-array-json.js";
|
|
13
|
-
/** Builder
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
/** Builder that returns a factory function called by the runner for each
|
|
14
|
+
* migration. dbRunner may be a top-level connection or a transaction—read
|
|
15
|
+
* helpers use it directly, while systemWriteAs delegates to the dispatcher.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* runner.integration.test.ts
|
|
17
|
+
* dispatcher.write does not receive the runner transaction: runBatch opens
|
|
18
|
+
* a separate transaction on context.db (using a separate pool connection).
|
|
19
|
+
* Writes therefore commit independently: a runner failure rolls back only
|
|
20
|
+
* the marker insert and dbRunner reads; dispatcher writes remain committed.
|
|
21
|
+
* Seeds must therefore be idempotent (verified in
|
|
22
|
+
* runner.integration.test.ts “dispatcher-writes vor throw bleiben committed”). */
|
|
23
23
|
export function createSeedMigrationContext(args) {
|
|
24
|
-
// Default
|
|
25
|
-
// text-content, etc.).
|
|
26
|
-
//
|
|
24
|
+
// Default executor for system-scope aggregates (config-values, system
|
|
25
|
+
// text-content, etc.). For tenant-scope aggregates, the caller must
|
|
26
|
+
// explicitly provide `tenantIdOverride`—see the documentation in types.ts.
|
|
27
27
|
const defaultSystemUser = createSystemUser(SYSTEM_TENANT_ID);
|
|
28
28
|
return {
|
|
29
29
|
systemWriteAs: async (handlerQualifiedName, payload, tenantIdOverride, extraRoles) => {
|
|
30
|
-
// tenantIdOverride
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// feedback_event_store_tenant_consistency.md).
|
|
30
|
+
// tenantIdOverride creates a system user with the stream tenant ID so that
|
|
31
|
+
// the event-store executor finds the aggregate in the correct stream.
|
|
32
|
+
// This prevents the version_conflict failure.
|
|
34
33
|
const executor = tenantIdOverride !== undefined || (extraRoles && extraRoles.length > 0)
|
|
35
34
|
? createSystemUser(tenantIdOverride ?? SYSTEM_TENANT_ID, extraRoles)
|
|
36
35
|
: defaultSystemUser;
|
|
37
36
|
const result = await args.dispatcher.write(handlerQualifiedName, payload, executor);
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
37
|
+
// Without this check, WriteResult{isSuccess: false} would pass silently,
|
|
38
|
+
// causing the marker to be written despite the failed write and the
|
|
39
|
+
// migration to be incorrectly marked as "applied". Throw so the
|
|
40
|
+
// runner's outer transaction rolls back and the marker is not written.
|
|
41
|
+
// Seed authors can use try/catch for custom handling when a soft failure
|
|
42
|
+
// is expected.
|
|
43
43
|
if (!result.isSuccess) {
|
|
44
44
|
const code = result.error?.code ?? "unknown";
|
|
45
45
|
const message = result.error?.message ?? "(no message)";
|
package/dist/es-ops/index.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
// es-ops — ES
|
|
1
|
+
// es-ops — ES operations pattern for Kumiko apps.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// (kumiko_es_operations
|
|
3
|
+
// Seed migrations use the same infrastructure as later operation types
|
|
4
|
+
// (the `kumiko_es_operations` table with an `operation_type` discriminator).
|
|
5
5
|
//
|
|
6
|
-
// App
|
|
7
|
-
// - SeedMigration
|
|
8
|
-
// - runProdApp({ seedsDir })
|
|
9
|
-
// - bunx kumiko ops seed:new|status|apply (CLI)
|
|
10
|
-
//
|
|
11
|
-
// Plan-Doc: kumiko-platform/docs/plans/features/es-ops.md
|
|
6
|
+
// App author API:
|
|
7
|
+
// - `SeedMigration`: default export type of a seed file
|
|
8
|
+
// - `runProdApp({ seedsDir })`: the framework runs pending migrations at boot
|
|
9
|
+
// - `bunx kumiko ops seed:new|status|apply` (CLI)
|
|
12
10
|
export { createSeedMigrationContext, } from "./context.js";
|
|
13
11
|
export { createEsOperationsTable, esOperationsTable, } from "./operations-schema.js";
|
|
14
12
|
export { runPendingSeedMigrations, } from "./runner.js";
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// File-ID-Tracking analog drizzle-kit: filename ist die ID, applied-set
|
|
6
|
-
// liegt in dieser Tabelle. Pending = files-on-disk MINUS applied-set.
|
|
7
|
-
//
|
|
8
|
-
// operation_type-Discriminator lässt Phase 2+ dieselbe Tabelle nutzen,
|
|
9
|
-
// kein Schema-Sprawl. CLI-Status filtert per type:
|
|
1
|
+
// Tracks ES operations. The filename is the ID, so pending operations are files on disk minus the
|
|
2
|
+
// applied set. The operation_type discriminator lets different operation types share one table
|
|
3
|
+
// without schema sprawl; CLI status commands filter by type:
|
|
10
4
|
// bunx kumiko ops seed:status → operation_type = "seed-migration"
|
|
11
5
|
// bunx kumiko ops projection:status → operation_type = "projection-rebuild"
|
|
12
6
|
import { index, integer, table as pgTable, sql, text, timestamp } from "../db/dialect.js";
|
|
@@ -24,7 +18,7 @@ export const esOperationsTable = pgTable("kumiko_es_operations", {
|
|
|
24
18
|
.default(sql `now()`),
|
|
25
19
|
durationMs: integer("duration_ms").notNull(),
|
|
26
20
|
// Trace: came from boot-time auto-apply, explicit CLI, or CI step.
|
|
27
|
-
//
|
|
21
|
+
// Supports forensic questions about who initiated the operation and when.
|
|
28
22
|
appliedBy: text("applied_by").notNull().$type(),
|
|
29
23
|
// Optional human-readable annotation — surfaced in `ops <op>:status`.
|
|
30
24
|
notes: text("notes"),
|
package/dist/es-ops/runner.d.ts
CHANGED
|
@@ -3,23 +3,23 @@ import type { Registry } from "../engine/index.ts";
|
|
|
3
3
|
import type { EsOperationAppliedBy, SeedMigrationContext } from "./types.ts";
|
|
4
4
|
export type RunPendingSeedMigrationsArgs = {
|
|
5
5
|
readonly db: DbConnection;
|
|
6
|
-
/**
|
|
6
|
+
/** Absolute path to the seeds directory (typically <appRoot>/seeds). */
|
|
7
7
|
readonly seedsDir: string;
|
|
8
|
-
/** Factory
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
/** Factory that creates the context for each seed migration.
|
|
9
|
+
* The caller receives a DbRunner (connection OR active transaction); the
|
|
10
|
+
* runner invokes this once per migration within the transaction scope. */
|
|
11
11
|
readonly createContext: (dbRunner: DbRunner) => SeedMigrationContext;
|
|
12
12
|
/** Trace-marker: boot | cli | ci-pipeline. Landet in applied_by. */
|
|
13
13
|
readonly appliedBy: EsOperationAppliedBy;
|
|
14
|
-
/** Optional registry
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
/** Optional registry for dry-run validation: parses each seed file and
|
|
15
|
+
* checks that all referenced handler QNs exist in the registry
|
|
16
|
+
* BEFORE the migration runs. This catches camelCase typos and other QN
|
|
17
|
+
* drift at boot rather than in the middle of a write cycle.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* If omitted, no dry run is performed (backward compatibility for tests
|
|
20
|
+
* that do not provide a registry). runProdApp supplies the correct one. */
|
|
21
21
|
readonly registry?: Registry;
|
|
22
|
-
/** Optional
|
|
22
|
+
/** Optional line sink; the default logs under the "es-ops/seed-migration" namespace. */
|
|
23
23
|
readonly logger?: (line: string) => void;
|
|
24
24
|
};
|
|
25
25
|
export type RunPendingSeedMigrationsResult = {
|
package/dist/es-ops/runner.js
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
// Runner
|
|
1
|
+
// Runner for pending seed migrations during boot.
|
|
2
2
|
//
|
|
3
3
|
// Flow:
|
|
4
|
-
// 1. List seeds/<*.ts> sorted ascending (filename =
|
|
4
|
+
// 1. List seeds/<*.ts> sorted ascending (filename = chronological ID)
|
|
5
5
|
// 2. SELECT id FROM kumiko_es_operations WHERE operation_type='seed-migration'
|
|
6
|
-
// 3. pending = files
|
|
7
|
-
// 4.
|
|
8
|
-
// a. dynamic import → default
|
|
9
|
-
// b.
|
|
10
|
-
// →
|
|
11
|
-
// c.
|
|
6
|
+
// 3. pending = files \\ applied
|
|
7
|
+
// 4. For each pending migration in order:
|
|
8
|
+
// a. dynamic import → default export (SeedMigration)
|
|
9
|
+
// b. if migration.skippable && env[KUMIKO_SKIP_ES_OPS_<sanitized>]='1':
|
|
10
|
+
// → log line + continue (no marker written)
|
|
11
|
+
// c. Start transaction
|
|
12
12
|
// d. await migration.run(ctx)
|
|
13
|
-
// e. INSERT marker
|
|
14
|
-
// f.
|
|
15
|
-
// 5. On any failure:
|
|
16
|
-
// → App
|
|
13
|
+
// e. INSERT marker with duration_ms + appliedBy
|
|
14
|
+
// f. Commit transaction
|
|
15
|
+
// 5. On any failure: transaction rollback + error log + throw
|
|
16
|
+
// → App boot aborts. The operator must fix the failure and retry.
|
|
17
17
|
//
|
|
18
|
-
//
|
|
19
|
-
// `KUMIKO_SKIP_ES_OPS_<id>=1` env
|
|
20
|
-
//
|
|
21
|
-
// Standard-Workflow — wirklich Notfall.
|
|
18
|
+
// Emergency skip pattern: seed.skippable=true permits a
|
|
19
|
+
// `KUMIKO_SKIP_ES_OPS_<id>=1` env flag to skip a broken migration
|
|
20
|
+
// without touching its code. NOT a standard workflow — emergency use only.
|
|
22
21
|
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
23
22
|
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
24
23
|
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
@@ -31,32 +30,30 @@ import { readdir, readFile } from "node:fs/promises";
|
|
|
31
30
|
import path from "node:path";
|
|
32
31
|
import { acquireEsOpsAdvisoryLock, esOperationExists } from "../db/queries/es-ops.js";
|
|
33
32
|
import { insertOne, selectMany } from "../db/query.js";
|
|
33
|
+
import { createFallbackLogger } from "../logging/utils.js";
|
|
34
34
|
import { esOperationsTable } from "./operations-schema.js";
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
console.log(line);
|
|
38
|
-
};
|
|
39
|
-
const LOG_PREFIX = "[es-ops/seed-migration]";
|
|
35
|
+
const seedMigrationLog = createFallbackLogger("es-ops/seed-migration");
|
|
36
|
+
const DEFAULT_LOGGER = (line) => seedMigrationLog.info(line);
|
|
40
37
|
export async function runPendingSeedMigrations(args) {
|
|
41
38
|
const log = args.logger ?? DEFAULT_LOGGER;
|
|
42
39
|
const onDisk = await listSeedFiles(args.seedsDir);
|
|
43
40
|
if (onDisk.length === 0) {
|
|
44
|
-
log(
|
|
41
|
+
log(`no seed files in ${args.seedsDir} — skipping`);
|
|
45
42
|
return { appliedIds: [], skippedIds: [] };
|
|
46
43
|
}
|
|
47
44
|
const applied = await loadAppliedIds(args.db);
|
|
48
45
|
const pending = onDisk.filter((entry) => !applied.has(entry.id));
|
|
49
46
|
if (pending.length === 0) {
|
|
50
|
-
log(`${
|
|
47
|
+
log(`${onDisk.length} on disk, all applied — nothing to do`);
|
|
51
48
|
return { appliedIds: [], skippedIds: [] };
|
|
52
49
|
}
|
|
53
|
-
log(`${
|
|
50
|
+
log(`${pending.length}/${onDisk.length} pending`);
|
|
54
51
|
const appliedIds = [];
|
|
55
52
|
const skippedIds = [];
|
|
56
|
-
// Dry-
|
|
57
|
-
//
|
|
58
|
-
// write
|
|
59
|
-
// "handler not found"
|
|
53
|
+
// Dry-run pass: before any migration, parse all handler QNs from the seed
|
|
54
|
+
// files and check them against the registry. Fail fast before the first
|
|
55
|
+
// write, with a clear error containing the file and QN instead of a runtime
|
|
56
|
+
// "handler not found" error in the middle of the migration flow.
|
|
60
57
|
if (args.registry !== undefined) {
|
|
61
58
|
const unknownQns = [];
|
|
62
59
|
for (const entry of pending) {
|
|
@@ -71,27 +68,26 @@ export async function runPendingSeedMigrations(args) {
|
|
|
71
68
|
const lines = unknownQns.map((u) => ` - ${u.id}: "${u.qn}" not registered`);
|
|
72
69
|
throw new Error(`[es-ops/seed-migration] dry-run found ${unknownQns.length} unknown handler-QN(s):\n${lines.join("\n")}\n Check spelling against your TenantHandlers/AuthHandlers constants (kebab-case after the colon).`);
|
|
73
70
|
}
|
|
74
|
-
log(
|
|
71
|
+
log(`dry-run ok — all referenced handler-QNs registered`);
|
|
75
72
|
}
|
|
76
73
|
for (const entry of pending) {
|
|
77
74
|
const migration = await loadSeedModule(entry.filePath);
|
|
78
75
|
const envFlag = `KUMIKO_SKIP_ES_OPS_${sanitizeForEnv(entry.id)}`;
|
|
79
76
|
if (migration.skippable === true && process.env[envFlag] === "1") {
|
|
80
|
-
log(
|
|
77
|
+
log(`skip "${entry.id}" — ${envFlag}=1`);
|
|
81
78
|
skippedIds.push(entry.id);
|
|
82
79
|
continue;
|
|
83
80
|
}
|
|
84
81
|
const start = Date.now();
|
|
85
82
|
try {
|
|
86
83
|
await args.db.begin(async (tx) => {
|
|
87
|
-
// Advisory
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// automatisch released (xact-scope).
|
|
84
|
+
// Advisory lock: serializes multi-replica boots. The second pod blocks until
|
|
85
|
+
// the first finishes, then rechecks its applied set in the next
|
|
86
|
+
// iteration and finds the marker, so it skips the migration. The lock
|
|
87
|
+
// is released automatically when the transaction commits (xact scope).
|
|
92
88
|
await acquireEsOpsAdvisoryLock(tx);
|
|
93
89
|
if (await esOperationExists(tx, entry.id)) {
|
|
94
|
-
log(
|
|
90
|
+
log(`race-skip "${entry.id}" — applied by parallel boot`);
|
|
95
91
|
// skip: parallel boot won the advisory-lock and already applied
|
|
96
92
|
// this migration. Nothing more to do in this tx.
|
|
97
93
|
return;
|
|
@@ -107,13 +103,13 @@ export async function runPendingSeedMigrations(args) {
|
|
|
107
103
|
});
|
|
108
104
|
});
|
|
109
105
|
const elapsed = Date.now() - start;
|
|
110
|
-
log(
|
|
106
|
+
log(`✓ ${entry.id} (${elapsed}ms) — ${migration.description}`);
|
|
111
107
|
appliedIds.push(entry.id);
|
|
112
108
|
}
|
|
113
109
|
catch (err) {
|
|
114
110
|
const elapsed = Date.now() - start;
|
|
115
|
-
log(
|
|
116
|
-
log(
|
|
111
|
+
log(`✗ ${entry.id} (${elapsed}ms) — ${stringifyError(err)}`);
|
|
112
|
+
log(`ABORT — ${pending.length - appliedIds.length - skippedIds.length - 1} pending migrations were NOT attempted`);
|
|
117
113
|
throw err;
|
|
118
114
|
}
|
|
119
115
|
}
|
|
@@ -125,8 +121,8 @@ async function listSeedFiles(seedsDir) {
|
|
|
125
121
|
entries = await readdir(seedsDir);
|
|
126
122
|
}
|
|
127
123
|
catch {
|
|
128
|
-
//
|
|
129
|
-
//
|
|
124
|
+
// A missing directory is treated as empty. An app without a seeds directory
|
|
125
|
+
// is a valid no-op state.
|
|
130
126
|
return [];
|
|
131
127
|
}
|
|
132
128
|
return entries
|
|
@@ -135,11 +131,11 @@ async function listSeedFiles(seedsDir) {
|
|
|
135
131
|
.sort() // filename = chronologische ID (date-prefix-convention)
|
|
136
132
|
.map((name) => ({
|
|
137
133
|
id: name.replace(/\.(ts|mts|js)$/, ""),
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// "Cannot find module 'seeds/...' from '<runner-path>'".
|
|
134
|
+
// Resolve rather than join: Bun's await import() requires absolute paths.
|
|
135
|
+
// If seedsDir is relative (for example "./seeds" from a runProdApp
|
|
136
|
+
// option), the joined path would also be relative. Bun's import resolver
|
|
137
|
+
// would resolve it relative to runner.ts rather than process.cwd(),
|
|
138
|
+
// causing "Cannot find module 'seeds/...' from '<runner-path>'".
|
|
143
139
|
filePath: path.resolve(seedsDir, name),
|
|
144
140
|
}));
|
|
145
141
|
}
|
|
@@ -171,16 +167,16 @@ function isSeedMigration(value) {
|
|
|
171
167
|
function sanitizeForEnv(id) {
|
|
172
168
|
return id.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
173
169
|
}
|
|
174
|
-
// Parse seed-file source
|
|
175
|
-
//
|
|
170
|
+
// Parse seed-file source and extract handler QNs from `systemWriteAs(...)`
|
|
171
|
+
// calls. A regex (rather than an AST) handles common inline string cases:
|
|
176
172
|
// ctx.systemWriteAs("foo:write:bar", payload)
|
|
177
173
|
// systemWriteAs("foo:write:bar", ...) (destructured)
|
|
178
174
|
//
|
|
179
|
-
//
|
|
180
|
-
// - QN
|
|
181
|
-
// - String
|
|
182
|
-
//
|
|
183
|
-
//
|
|
175
|
+
// Unsupported cases:
|
|
176
|
+
// - QN from a variable: `const qn = "..."; ctx.systemWriteAs(qn, ...)`
|
|
177
|
+
// - String concatenation or template literals with dynamic variables
|
|
178
|
+
// These patterns are uncommon in real seed migrations. This limitation is
|
|
179
|
+
// documented so callers requiring dynamic QNs understand the constraint.
|
|
184
180
|
function extractWriteHandlerQns(source) {
|
|
185
181
|
const pattern = /systemWriteAs\s*\(\s*["']([^"']+)["']/g;
|
|
186
182
|
const out = new Set();
|