@cosmicdrift/kumiko-framework 0.385.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/request-context.d.ts +2 -0
- package/dist/api/request-context.js +16 -7
- 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/cross-tenant-reads.d.ts +8 -0
- package/dist/db/cross-tenant-reads.js +23 -0
- 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/queries/test-stack.d.ts +4 -1
- package/dist/db/queries/test-stack.js +15 -2
- 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.d.ts +8 -0
- 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/internal/stack-support.d.ts +1 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/jobs/job-runner-helpers.d.ts +1 -2
- package/dist/jobs/job-runner-helpers.js +8 -18
- 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 +17 -18
- 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 +42 -19
- 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 +54 -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
package/dist/jobs/job-runner.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
-
import { escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
+
import { crossTenantReadsGrantFor, escapeHatchFor } from "@cosmicdrift/kumiko-types/handlers";
|
|
3
3
|
import { DelayedError, Queue, UnrecoverableError, Worker } from "bullmq";
|
|
4
4
|
import { Redis } from "ioredis";
|
|
5
5
|
import { requestContext } from "../api/request-context.js";
|
|
6
|
+
import { createCrossTenantReads } from "../db/cross-tenant-reads.js";
|
|
6
7
|
import { createTenantDb, createUncheckedSystemDb } from "../db/tenant-db.js";
|
|
7
8
|
import { createDerivativesContext } from "../derivatives/derivatives-context.js";
|
|
8
9
|
import { bridgeStub } from "../engine/bridge-stub.js";
|
|
@@ -19,7 +20,7 @@ import { RedisKeys } from "../pipeline/redis-keys.js";
|
|
|
19
20
|
import { createGatedIdentitySwitch } from "../pipeline/system-identity-switch.js";
|
|
20
21
|
import { buildPersonalDataGate, parseWriteOrigin } from "../pipeline/write-origin.js";
|
|
21
22
|
import { cronRepeatOptions } from "./cron-repeat-options.js";
|
|
22
|
-
import { BOOT_GATE_SLOW_WARN_MS, BOOT_REDIS_TIMEOUT_MS, buildRetryBullOpts, COMPLETED_JOB_RETENTION_AGE_SEC, captureTraceContext,
|
|
23
|
+
import { BOOT_GATE_SLOW_WARN_MS, BOOT_REDIS_TIMEOUT_MS, buildRetryBullOpts, COMPLETED_JOB_RETENTION_AGE_SEC, captureTraceContext, createJobLogger, DEFAULT_MAX_DEFERS, DEFER_BACKOFF_MAX_DOUBLINGS, DEFER_COUNT_KEY, DEFER_DELAY_CAP_MS, FAILED_JOB_RETENTION_AGE_SEC, HANDLER_DEFER_COUNT_KEY, isJobTriggerEvent, jobSubjectKey, maxRetryWindowMs, missingDbProxy, parseRedisOpts, payloadMatchesTriggerFilter, priorAttemptsOf, readTraceContext, resolveJobTz, STOP_QUEUE_READY_TIMEOUT_MS, stampGatedWriteOrigin, startQueueDepthPolling, TRACE_CONTEXT_KEY, tenantFailureMessageKey, timeoutReject, } from "./job-runner-helpers.js";
|
|
23
24
|
import { ACTIVE_TENANT_IDS_QUERY_NAME, bootJobIdForJobName, DEFAULT_QUEUE_NAME_PREFIX, markSchedulersSeen, ORPHAN_SCHEDULER_GRACE_MS, perTenantChildJobId, queueNameFor, removeOrphanJobSchedulers, schedulerIdForJobName, } from "./job-runner-schedulers.js";
|
|
24
25
|
import { resolveMaxPerTenant } from "./max-per-tenant.js";
|
|
25
26
|
export { serializeJobSubject } from "./job-runner-helpers.js";
|
|
@@ -138,8 +139,7 @@ export function createJobRunner(options) {
|
|
|
138
139
|
lockRedis = new Redis(redisOpts);
|
|
139
140
|
// Without a listener, a post-close 'error' (e.g. a teardown-race
|
|
140
141
|
// "Connection is closed") is unhandled and crashes the process — in
|
|
141
|
-
// bun:test it gets attributed to whichever test happens to run next
|
|
142
|
-
// (fw#1805).
|
|
142
|
+
// bun:test it gets attributed to whichever test happens to run next.
|
|
143
143
|
lockRedis.on("error", (err) => {
|
|
144
144
|
const log = stopping ? errorLogger.debug : errorLogger.error;
|
|
145
145
|
log("lock redis connection error", { error: err.message });
|
|
@@ -180,7 +180,7 @@ export function createJobRunner(options) {
|
|
|
180
180
|
}),
|
|
181
181
|
};
|
|
182
182
|
// Same unhandled-'error'-crash hazard as lockRedis above, just via
|
|
183
|
-
// BullMQ's internal ioredis client
|
|
183
|
+
// BullMQ's internal ioredis client.
|
|
184
184
|
for (const queue of Object.values(queues)) {
|
|
185
185
|
queue.on("error", (err) => {
|
|
186
186
|
const log = stopping ? errorLogger.debug : errorLogger.error;
|
|
@@ -556,7 +556,7 @@ export function createJobRunner(options) {
|
|
|
556
556
|
// Same buildHandlerContext parity as ctx.files above: ctx.notify/ctx.config
|
|
557
557
|
// must resolve for jobs the same way they do for write-handlers, or an
|
|
558
558
|
// app-author job calling ctx.notify(...)/ctx.config(...) hits a TypeError
|
|
559
|
-
// at runtime the write-handler path never would
|
|
559
|
+
// at runtime the write-handler path never would.
|
|
560
560
|
const notify = context._notifyFactory?.(jobSystemUser, tenantId, selfRunner, undefined, context._fileProviderResolver);
|
|
561
561
|
const configDb = context.db; // @cast-boundary db-operator
|
|
562
562
|
// Shared by the config accessor and ctx.derivatives below — both need the
|
|
@@ -611,7 +611,7 @@ export function createJobRunner(options) {
|
|
|
611
611
|
},
|
|
612
612
|
})
|
|
613
613
|
: undefined;
|
|
614
|
-
const crossTenantReadsReason =
|
|
614
|
+
const crossTenantReadsReason = crossTenantReadsGrantFor(jobDef.escapeHatch)?.reason;
|
|
615
615
|
const crossTenantReads = tenantScopedDb && crossTenantReadsReason !== undefined
|
|
616
616
|
? createCrossTenantReads(tenantScopedDb, crossTenantReadsReason, reportEscapeHatch)
|
|
617
617
|
: undefined;
|
|
@@ -738,7 +738,7 @@ export function createJobRunner(options) {
|
|
|
738
738
|
const handlerResult = await requestContext.run({
|
|
739
739
|
requestId: jobRequestId,
|
|
740
740
|
correlationId: jobCorrelationId,
|
|
741
|
-
//
|
|
741
|
+
// Events a job writes carry the job as their origin.
|
|
742
742
|
handler: jobName,
|
|
743
743
|
feature: registry.getJobFeature(jobName) ?? qnScope(jobName),
|
|
744
744
|
writeOrigin: jobOrigin,
|
|
@@ -793,7 +793,7 @@ export function createJobRunner(options) {
|
|
|
793
793
|
"job.id": jobId,
|
|
794
794
|
"job.attempt": attempt,
|
|
795
795
|
"kumiko.tenant_id": tenantId,
|
|
796
|
-
// Lane-routing attributes
|
|
796
|
+
// Lane-routing attributes. `run_in` is the job's
|
|
797
797
|
// declared lane (explicit or default-"worker"); `consumer_lane`
|
|
798
798
|
// is which runner actually executed it. They diverge in
|
|
799
799
|
// all-in-one (both lanes live in one process) but must match
|
|
@@ -839,7 +839,7 @@ export function createJobRunner(options) {
|
|
|
839
839
|
// Boot gates run inline and are awaited, so a throwing gate rejects start()
|
|
840
840
|
// — and with it the entrypoint's start() and runProdApp's boot, before the
|
|
841
841
|
// process reports ready. Enqueuing them (as runOnBoot does) would only fail
|
|
842
|
-
// a queue job and let the deploy through
|
|
842
|
+
// a queue job and let the deploy through.
|
|
843
843
|
async function runBootGates(lane) {
|
|
844
844
|
for (const [name, jobDef] of allJobs) {
|
|
845
845
|
if (laneForJob(jobDef) !== lane)
|
|
@@ -977,7 +977,7 @@ export function createJobRunner(options) {
|
|
|
977
977
|
// still-settling blocking connection: it rejects in-flight commands
|
|
978
978
|
// via ioredis's flushQueue() during close(), which isn't a listenable
|
|
979
979
|
// 'error' event — the only fix is to not return until both of the
|
|
980
|
-
// worker's connections (main + blocking) are ready
|
|
980
|
+
// worker's connections (main + blocking) are ready. This
|
|
981
981
|
// mirrors the wait BullMQ already does internally for
|
|
982
982
|
// upsertJobScheduler()/add() below when the lane has a cron/boot job.
|
|
983
983
|
// Racing a timeout against it keeps an unreachable Redis from hanging
|
|
@@ -1069,7 +1069,7 @@ export function createJobRunner(options) {
|
|
|
1069
1069
|
}
|
|
1070
1070
|
// BullMQ close() while a Queue's connection is still 'initializing'
|
|
1071
1071
|
// rejects the in-flight INFO with "Connection is closed." on an emitter
|
|
1072
|
-
// without listeners -> unhandled rejection
|
|
1072
|
+
// without listeners -> unhandled rejection. Wait for readiness
|
|
1073
1073
|
// first; a failed/timed-out wait must not block the close itself.
|
|
1074
1074
|
const stopReadyTimeoutMs = Math.min(bootRedisTimeoutMs, STOP_QUEUE_READY_TIMEOUT_MS);
|
|
1075
1075
|
await settleAbandonedBootGates(stopReadyTimeoutMs);
|
|
@@ -1202,8 +1202,8 @@ export function createJobRunner(options) {
|
|
|
1202
1202
|
for (const [name, jobDef] of allJobs) {
|
|
1203
1203
|
if (!("on" in jobDef.trigger))
|
|
1204
1204
|
continue;
|
|
1205
|
-
// skip:
|
|
1206
|
-
//
|
|
1205
|
+
// skip: other trigger types (cron, manual) do not respond to events.
|
|
1206
|
+
// Only "on" triggers are matched here.
|
|
1207
1207
|
const triggerOn = jobDef.trigger.on;
|
|
1208
1208
|
const matches = Array.isArray(triggerOn)
|
|
1209
1209
|
? triggerOn.includes(eventName)
|
|
@@ -1217,10 +1217,9 @@ export function createJobRunner(options) {
|
|
|
1217
1217
|
data["_tenantId"] = user.tenantId;
|
|
1218
1218
|
data["_triggeredById"] = user.id;
|
|
1219
1219
|
}
|
|
1220
|
-
// Multi-
|
|
1221
|
-
//
|
|
1222
|
-
//
|
|
1223
|
-
// (kein "ist es Multi?"-Branch nötig).
|
|
1220
|
+
// Multi-trigger: add _triggerName to the payload so the handler knows which
|
|
1221
|
+
// of the N triggers fired. Set it for single triggers too; this costs
|
|
1222
|
+
// nothing and keeps handler code simple (no "is it multi?" branch).
|
|
1224
1223
|
data["_triggerName"] = eventName;
|
|
1225
1224
|
if (triggerEvent)
|
|
1226
1225
|
data["_triggerEvent"] = triggerEvent;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Process lifecycle: 4-state machine + LIFO shutdown hooks.
|
|
2
2
|
// Signal wiring lives in signal-handlers.ts; v1 scope in architecture/lifecycle.md.
|
|
3
|
+
import { createFallbackLogger } from "../logging/utils.js";
|
|
3
4
|
const DEFAULT_DRAIN_TIMEOUT_MS = 40_000;
|
|
4
5
|
export function createLifecycle(opts = {}) {
|
|
5
6
|
const now = opts.now ?? (() => Date.now());
|
|
@@ -103,9 +104,6 @@ export function createLifecycle(opts = {}) {
|
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
106
|
function makeErrorLogger(logger) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
// biome-ignore lint/suspicious/noConsole: ops-visible fallback when no logger is wired
|
|
110
|
-
return (msg, err) => console.error(`[lifecycle] ${msg}:`, err);
|
|
107
|
+
const log = createFallbackLogger("lifecycle", logger);
|
|
108
|
+
return (msg, err) => log.error(msg, { err });
|
|
111
109
|
}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
export function attachSignalHandlers(lifecycle, opts = {}) {
|
|
5
5
|
const signals = opts.signals ?? ["SIGTERM", "SIGINT"];
|
|
6
6
|
const exitFn = opts.exit ?? ((code) => process.exit(code));
|
|
7
|
+
// bun-types vs @types/node: signal overload on process.on/off is not
|
|
8
|
+
// selectable via bind() under the merged types — EventEmitter seam is intentional.
|
|
9
|
+
const processEmitter = process;
|
|
7
10
|
const listeners = new Map();
|
|
8
11
|
// Guard so double-SIGTERM doesn't call exitFn twice. drain() is already
|
|
9
12
|
// idempotent via its shared promise, but its .then/.catch chain would fire
|
|
@@ -24,15 +27,13 @@ export function attachSignalHandlers(lifecycle, opts = {}) {
|
|
|
24
27
|
.then(() => exitFn(0))
|
|
25
28
|
.catch(() => exitFn(1));
|
|
26
29
|
};
|
|
27
|
-
|
|
28
|
-
// selectable via bind() under the merged types — EventEmitter seam is intentional.
|
|
29
|
-
process.on(sig, handler);
|
|
30
|
+
processEmitter.on(sig, handler);
|
|
30
31
|
listeners.set(sig, handler);
|
|
31
32
|
}
|
|
32
33
|
return {
|
|
33
34
|
detach: () => {
|
|
34
35
|
for (const [sig, handler] of listeners) {
|
|
35
|
-
|
|
36
|
+
processEmitter.off(sig, handler);
|
|
36
37
|
}
|
|
37
38
|
listeners.clear();
|
|
38
39
|
},
|
package/dist/logging/utils.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Logger } from "./types.ts";
|
|
2
2
|
type FallbackLogger = {
|
|
3
|
+
info(msg: string, data?: Record<string, unknown>): void;
|
|
3
4
|
error(msg: string, data?: Record<string, unknown>): void;
|
|
4
5
|
warn(msg: string, data?: Record<string, unknown>): void;
|
|
5
6
|
debug(msg: string, data?: Record<string, unknown>): void;
|
|
6
7
|
};
|
|
7
8
|
export declare function setProcessLogger(logger: Logger | null): void;
|
|
8
|
-
export declare function createFallbackLogger(namespace: string, logger?: (Pick<Logger, "error"> & Partial<Pick<Logger, "warn" | "debug">>) | undefined): FallbackLogger;
|
|
9
|
+
export declare function createFallbackLogger(namespace: string, logger?: (Pick<Logger, "error"> & Partial<Pick<Logger, "info" | "warn" | "debug" | "child">>) | undefined): FallbackLogger;
|
|
9
10
|
export {};
|
package/dist/logging/utils.js
CHANGED
|
@@ -3,9 +3,33 @@ let processLogger = null;
|
|
|
3
3
|
export function setProcessLogger(logger) {
|
|
4
4
|
processLogger = logger;
|
|
5
5
|
}
|
|
6
|
+
// Bun prints a trailing "undefined" when a second argument is passed explicitly as undefined.
|
|
7
|
+
function writeConsole(write, line, data) {
|
|
8
|
+
if (data === undefined)
|
|
9
|
+
write(line);
|
|
10
|
+
else
|
|
11
|
+
write(line, data);
|
|
12
|
+
}
|
|
6
13
|
export function createFallbackLogger(namespace, logger) {
|
|
14
|
+
if (logger?.child) {
|
|
15
|
+
const child = logger.child({ component: namespace });
|
|
16
|
+
return {
|
|
17
|
+
info: (msg, data) => child.info(msg, data),
|
|
18
|
+
error: (msg, data) => child.error(msg, data),
|
|
19
|
+
warn: (msg, data) => child.warn(msg, data),
|
|
20
|
+
debug: (msg, data) => child.debug(msg, data),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
7
23
|
if (logger) {
|
|
8
24
|
return {
|
|
25
|
+
info(msg, data) {
|
|
26
|
+
if (logger.info) {
|
|
27
|
+
logger.info(`[${namespace}] ${msg}`, data);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
writeConsole(console.info, `[${namespace}] ${msg}`, data);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
9
33
|
error(msg, data) {
|
|
10
34
|
logger.error(`[${namespace}] ${msg}`, data);
|
|
11
35
|
},
|
|
@@ -14,8 +38,7 @@ export function createFallbackLogger(namespace, logger) {
|
|
|
14
38
|
logger.warn(`[${namespace}] ${msg}`, data);
|
|
15
39
|
}
|
|
16
40
|
else {
|
|
17
|
-
|
|
18
|
-
console.warn(`[${namespace}] ${msg}`, data);
|
|
41
|
+
writeConsole(console.warn, `[${namespace}] ${msg}`, data);
|
|
19
42
|
}
|
|
20
43
|
},
|
|
21
44
|
debug(msg, data) {
|
|
@@ -23,8 +46,7 @@ export function createFallbackLogger(namespace, logger) {
|
|
|
23
46
|
logger.debug(`[${namespace}] ${msg}`, data);
|
|
24
47
|
}
|
|
25
48
|
else {
|
|
26
|
-
|
|
27
|
-
console.debug(`[${namespace}] ${msg}`, data);
|
|
49
|
+
writeConsole(console.debug, `[${namespace}] ${msg}`, data);
|
|
28
50
|
}
|
|
29
51
|
},
|
|
30
52
|
};
|
|
@@ -41,14 +63,22 @@ export function createFallbackLogger(namespace, logger) {
|
|
|
41
63
|
return cachedChild;
|
|
42
64
|
};
|
|
43
65
|
return {
|
|
66
|
+
info(msg, data) {
|
|
67
|
+
const child = resolveProcessChild();
|
|
68
|
+
if (child) {
|
|
69
|
+
child.info(msg, data);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
writeConsole(console.info, `[${namespace}] ${msg}`, data);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
44
75
|
error(msg, data) {
|
|
45
76
|
const child = resolveProcessChild();
|
|
46
77
|
if (child) {
|
|
47
78
|
child.error(msg, data);
|
|
48
79
|
}
|
|
49
80
|
else {
|
|
50
|
-
|
|
51
|
-
console.error(`[${namespace}] ${msg}`, data);
|
|
81
|
+
writeConsole(console.error, `[${namespace}] ${msg}`, data);
|
|
52
82
|
}
|
|
53
83
|
},
|
|
54
84
|
warn(msg, data) {
|
|
@@ -57,8 +87,7 @@ export function createFallbackLogger(namespace, logger) {
|
|
|
57
87
|
child.warn(msg, data);
|
|
58
88
|
}
|
|
59
89
|
else {
|
|
60
|
-
|
|
61
|
-
console.warn(`[${namespace}] ${msg}`, data);
|
|
90
|
+
writeConsole(console.warn, `[${namespace}] ${msg}`, data);
|
|
62
91
|
}
|
|
63
92
|
},
|
|
64
93
|
debug(msg, data) {
|
|
@@ -67,8 +96,7 @@ export function createFallbackLogger(namespace, logger) {
|
|
|
67
96
|
child.debug(msg, data);
|
|
68
97
|
}
|
|
69
98
|
else {
|
|
70
|
-
|
|
71
|
-
console.debug(`[${namespace}] ${msg}`, data);
|
|
99
|
+
writeConsole(console.debug, `[${namespace}] ${msg}`, data);
|
|
72
100
|
}
|
|
73
101
|
},
|
|
74
102
|
};
|
package/dist/migrations/index.js
CHANGED
|
@@ -4,5 +4,5 @@ export { assertKumikoSchemaCurrent, detectKumikoDrift, formatKumikoDriftReport,
|
|
|
4
4
|
export { createPendingRebuildsTable, enqueueProjectionRebuild, listPendingRebuilds, PROJECTION_REBUILD_JOB, pendingRebuildsTable, queueRebuildsFromMarkers, rebuildProjectionOrMultiStream, reportPendingRebuildRun, runPendingRebuilds, } from "./pending-rebuilds.js";
|
|
5
5
|
// Boot-time backfill: re-encrypts plaintext events after a PII annotation was added.
|
|
6
6
|
export { createPiiBackfillStateTable, piiAnnotationFingerprint, piiBackfillStateTable, runPiiEventBackfill, } from "./pii-event-backfill.js";
|
|
7
|
-
// tableName → projection-name,
|
|
7
|
+
// tableName → projection-name, for application-side projection rebuilds.
|
|
8
8
|
export { buildProjectionTableIndex } from "./projection-table-index.js";
|
|
@@ -28,18 +28,19 @@ export class SchemaDriftError extends Error {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
export async function detectKumikoDrift(db, migrationsDir) {
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// generate)
|
|
34
|
-
// status
|
|
35
|
-
//
|
|
36
|
-
//
|
|
31
|
+
// A newer framework version may not have generated `./kumiko/migrations` yet
|
|
32
|
+
// (for example, a first upgrade or custom schema setup without `kumiko schema
|
|
33
|
+
// generate`). Treat this as no local migrations and therefore no drift, matching
|
|
34
|
+
// the status subcommand's `existsSync` guard. Without it, `loadMigrationsFromDir`
|
|
35
|
+
// calls `readdirSync`, which throws synchronous ENOENT as a plain Error instead
|
|
36
|
+
// of SchemaDriftError and causes an unhandled boot failure.
|
|
37
37
|
if (!existsSync(migrationsDir)) {
|
|
38
38
|
return { ok: true, pending: [], checksumMismatches: [], missingTables: [], missingColumns: [] };
|
|
39
39
|
}
|
|
40
40
|
const local = loadMigrationsFromDir(migrationsDir);
|
|
41
|
-
//
|
|
42
|
-
//
|
|
41
|
+
// A fresh database may not have the tracking table because `kumiko schema apply`
|
|
42
|
+
// has never run. Treat that as no applied migrations, so all local migrations are
|
|
43
|
+
// pending rather than reporting an error.
|
|
43
44
|
const trackingExists = await tableExists(db, "_kumiko_migrations");
|
|
44
45
|
const applied = trackingExists
|
|
45
46
|
? new Map((await fetchAppliedMigrations(db)).map((a) => [a.id, a.checksum]))
|
|
@@ -117,11 +118,11 @@ export function formatKumikoDriftReport(report) {
|
|
|
117
118
|
lines.push(` - ${m.table}: ${m.columns.join(", ")}`);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// baseline
|
|
123
|
-
//
|
|
124
|
-
//
|
|
121
|
+
// Remediation depends on the cause: `kumiko schema apply` handles only pending
|
|
122
|
+
// migrations. A checksum mismatch causes MigrationChecksumMismatchError during
|
|
123
|
+
// apply, and baseline's ON CONFLICT DO NOTHING leaves it in alreadyTracked.
|
|
124
|
+
// A missing table with no pending migration was deleted after apply and cannot be
|
|
125
|
+
// restored by apply.
|
|
125
126
|
lines.push("");
|
|
126
127
|
if (report.pending.length > 0) {
|
|
127
128
|
lines.push("Run 'kumiko schema apply' to apply the pending migration(s).");
|
|
@@ -5,10 +5,10 @@ import type { Logger } from "../logging/types.ts";
|
|
|
5
5
|
import { type RebuildResult } from "../pipeline/index.ts";
|
|
6
6
|
export declare const pendingRebuildsTable: import("@cosmicdrift/kumiko-types/executor-brand").WritableSchemaTable;
|
|
7
7
|
export declare function createPendingRebuildsTable(db: DbConnection): Promise<void>;
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
/** Reads rebuild markers from freshly applied migrations and queues the
|
|
9
|
+
* affected tables. Upsert: an already-pending table keeps its queue slot
|
|
10
|
+
* (`queued_at` remains unchanged, preserving order), while `migration_id`
|
|
11
|
+
* points to the latest migration that flagged it for debugging. */
|
|
12
12
|
export declare function queueRebuildsFromMarkers(db: DbConnection, options: {
|
|
13
13
|
readonly migrationsDir: string;
|
|
14
14
|
readonly appliedIds: readonly string[];
|
|
@@ -21,7 +21,7 @@ export declare function listPendingRebuildRows(db: DbConnection): Promise<readon
|
|
|
21
21
|
export declare function listPendingRebuilds(db: DbConnection): Promise<readonly string[]>;
|
|
22
22
|
export declare function clearPendingRebuilds(db: DbConnection, rows: readonly PendingRebuildRow[]): Promise<void>;
|
|
23
23
|
export type PendingRebuildRun = {
|
|
24
|
-
/**
|
|
24
|
+
/** Projections rebuilt successfully (queue entries removed). */
|
|
25
25
|
readonly rebuilt: readonly {
|
|
26
26
|
readonly projection: string;
|
|
27
27
|
readonly eventsProcessed: number;
|
|
@@ -36,18 +36,19 @@ export type PendingRebuildRun = {
|
|
|
36
36
|
* marker) OR were deliberately DROPped by a later migration in the same
|
|
37
37
|
* run (no data loss, no projection left to resolve) — drained, silent. */
|
|
38
38
|
readonly unmapped: readonly string[];
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
/** Managed tables emptied by a marker in THIS run without a resolvable
|
|
40
|
+
* projection, indicating that the owning feature is missing from the
|
|
41
|
+
* composition. They are drained to avoid a stuck loop, but logged loudly
|
|
42
|
+
* because the projection is now empty. */
|
|
42
43
|
readonly unresolvedManaged: readonly string[];
|
|
43
44
|
};
|
|
44
45
|
export type RunPendingRebuildsOptions = {
|
|
45
|
-
/**
|
|
46
|
-
* (
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
/** Tables freshly queued via markers in THIS apply run
|
|
47
|
+
* (returned by `queueRebuildsFromMarkers`). Markers contain only managed
|
|
48
|
+
* tables, so one without a resolvable projection is a real defect (a
|
|
49
|
+
* missing feature) and is reported loudly instead of being silently
|
|
50
|
+
* drained. Without this option, every unmapped table is treated as
|
|
51
|
+
* pre-existing and benign. */
|
|
51
52
|
readonly thisRunTables?: readonly string[];
|
|
52
53
|
};
|
|
53
54
|
export declare function rebuildProjectionOrMultiStream(name: string, deps: {
|
|
@@ -63,12 +64,12 @@ export declare function rebuildProjectionOrMultiStream(name: string, deps: {
|
|
|
63
64
|
* failed tables stay queued. Shared by every `schema apply` entry point so the
|
|
64
65
|
* report and the error text cannot drift apart. */
|
|
65
66
|
export declare function reportPendingRebuildRun(run: PendingRebuildRun, log: (line: string) => void): void;
|
|
66
|
-
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
67
|
+
/** Processes the persistent queue: maps tables to projections, rebuilds each
|
|
68
|
+
* affected projection, and removes its tables from the queue only after
|
|
69
|
+
* SUCCESS. Failed tables remain pending and are retried by the next apply
|
|
70
|
+
* (or a direct re-call). Unmapped tables are drained to avoid a stuck loop;
|
|
71
|
+
* managed tables freshly emptied in this run without a projection are also
|
|
72
|
+
* reported through `unresolvedManaged`. */
|
|
72
73
|
export declare function runPendingRebuilds(db: DbConnection, registry: Registry, options?: RunPendingRebuildsOptions): Promise<PendingRebuildRun>;
|
|
73
74
|
export declare const PROJECTION_REBUILD_JOB = "jobs:job:projection-rebuild";
|
|
74
75
|
export type EnqueueProjectionRebuildResult = {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Persistent pending-rebuild queue for the `kumiko schema apply` path.
|
|
2
2
|
//
|
|
3
|
-
// Problem
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Problem: Apps read rebuild markers only from `result.applied` for the
|
|
4
|
+
// CURRENT apply round. If the projection rebuild failed (or the process
|
|
5
|
+
// crashed in between), the marker reference was gone on the next apply —
|
|
6
|
+
// `applied` was empty — leaving the projection silently incomplete without a
|
|
7
|
+
// self-service retry path.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
9
|
+
// Solution: persist the affected tables in `kumiko_pending_rebuilds` BEFORE
|
|
10
|
+
// the rebuild and delete them only after the associated projection rebuild
|
|
11
|
+
// succeeds. A subsequent apply, even without new migrations, processes open
|
|
12
|
+
// rebuilds through `runPendingRebuilds`.
|
|
13
13
|
import { instant, table as pgTable, sql, text } from "../db/dialect.js";
|
|
14
14
|
import { deleteMany, selectMany, upsertOnConflict } from "../db/query.js";
|
|
15
15
|
import { readRebuildMarker } from "../db/rebuild-marker.js";
|
|
@@ -24,15 +24,15 @@ export const pendingRebuildsTable = pgTable("kumiko_pending_rebuilds", {
|
|
|
24
24
|
queuedAt: instant("queued_at", { precision: 3 }).notNull().default(sql `now()`),
|
|
25
25
|
});
|
|
26
26
|
export async function createPendingRebuildsTable(db) {
|
|
27
|
-
// skip: table already exists — bootstrap
|
|
27
|
+
// skip: table already exists — bootstrap runs from multiple paths
|
|
28
28
|
if (await tableExists(db, "public.kumiko_pending_rebuilds"))
|
|
29
29
|
return;
|
|
30
30
|
await unsafePushTables(db, { kumikoPendingRebuilds: pendingRebuildsTable });
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
/** Reads rebuild markers from freshly applied migrations and queues the
|
|
33
|
+
* affected tables. Upsert: an already-pending table keeps its queue slot
|
|
34
|
+
* (`queued_at` remains unchanged, preserving order), while `migration_id`
|
|
35
|
+
* points to the latest migration that flagged it for debugging. */
|
|
36
36
|
export async function queueRebuildsFromMarkers(db, options) {
|
|
37
37
|
await createPendingRebuildsTable(db);
|
|
38
38
|
const queued = new Set();
|
|
@@ -58,7 +58,7 @@ export async function listPendingRebuilds(db) {
|
|
|
58
58
|
// table_name alone: a concurrent queueRebuildsFromMarkers can re-queue the same
|
|
59
59
|
// table for a NEWER migration (upsert bumps migration_id, keeps the slot)
|
|
60
60
|
// between the read and the clear. Scoping the delete to the snapshot's
|
|
61
|
-
// migration_id leaves that fresh re-queue intact instead of dropping it.
|
|
61
|
+
// migration_id leaves that fresh re-queue intact instead of dropping it.
|
|
62
62
|
export async function clearPendingRebuilds(db, rows) {
|
|
63
63
|
for (const { tableName, migrationId } of rows) {
|
|
64
64
|
await deleteMany(db, pendingRebuildsTable, { tableName, migrationId });
|
|
@@ -106,12 +106,12 @@ export function reportPendingRebuildRun(run, log) {
|
|
|
106
106
|
.join("; ")}. Table(s) stay queued in kumiko_pending_rebuilds — retried on the next apply.`);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
109
|
+
/** Processes the persistent queue: maps tables to projections, rebuilds each
|
|
110
|
+
* affected projection, and removes its tables from the queue only after
|
|
111
|
+
* SUCCESS. Failed tables remain pending and are retried by the next apply
|
|
112
|
+
* (or a direct re-call). Unmapped tables are drained to avoid a stuck loop;
|
|
113
|
+
* managed tables freshly emptied in this run without a projection are also
|
|
114
|
+
* reported through `unresolvedManaged`. */
|
|
115
115
|
export async function runPendingRebuilds(db, registry, options = {}) {
|
|
116
116
|
const pendingRows = await listPendingRebuildRows(db);
|
|
117
117
|
if (pendingRows.length === 0) {
|
|
@@ -119,7 +119,7 @@ export async function runPendingRebuilds(db, registry, options = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
// (table → migration) snapshot read together with the table list; the clear
|
|
121
121
|
// below scopes its DELETE to this migration_id so a concurrent re-queue for a
|
|
122
|
-
// newer migration survives instead of being dropped
|
|
122
|
+
// newer migration survives instead of being dropped.
|
|
123
123
|
const migrationByTable = new Map(pendingRows.map((r) => [r.tableName, r.migrationId]));
|
|
124
124
|
const snapshotRows = (tables) => tables.flatMap((tableName) => {
|
|
125
125
|
const migrationId = migrationByTable.get(tableName);
|
|
@@ -136,7 +136,7 @@ export async function runPendingRebuilds(db, registry, options = {}) {
|
|
|
136
136
|
if (projection === undefined) {
|
|
137
137
|
// Markers only carry managed tables (rebuild-marker.ts). A table emptied
|
|
138
138
|
// in THIS run with no resolving projection is a real defect (owning
|
|
139
|
-
// feature missing from the composition) → loud
|
|
139
|
+
// feature missing from the composition) → loud — unless the table
|
|
140
140
|
// no longer exists: then a later migration in the same run deliberately
|
|
141
141
|
// DROPped it after migrating its data elsewhere, no data loss. Pre-
|
|
142
142
|
// existing pending tables stay indistinguishable from old unmanaged
|
|
@@ -151,9 +151,9 @@ export async function runPendingRebuilds(db, registry, options = {}) {
|
|
|
151
151
|
}
|
|
152
152
|
byProjection.set(projection, [...(byProjection.get(projection) ?? []), tableName]);
|
|
153
153
|
}
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
154
|
+
// Both classes are drained: the queue must not grow indefinitely, and a
|
|
155
|
+
// re-apply must not remain stuck. The distinction is conveyed through the
|
|
156
|
+
// log and return field rather than by leaving entries queued.
|
|
157
157
|
const drained = [...unmapped, ...unresolvedManaged];
|
|
158
158
|
if (drained.length > 0) {
|
|
159
159
|
await clearPendingRebuilds(db, snapshotRows(drained));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Registry } from "../engine/types/feature.ts";
|
|
2
|
-
/** Index `tableName → projection-name`
|
|
3
|
-
* table
|
|
4
|
-
* Side-effect-only MSPs (table omitted)
|
|
2
|
+
/** Index `tableName → projection-name` from the registry. Only projections with
|
|
3
|
+
* a table definition (single-stream + multi-stream-with-table) are included.
|
|
4
|
+
* Side-effect-only MSPs (table omitted) cannot be rebuilt. */
|
|
5
5
|
export declare function buildProjectionTableIndex(registry: Registry): ReadonlyMap<string, string>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// Index `tableName → projection-name`
|
|
2
|
-
// app-
|
|
3
|
-
//
|
|
1
|
+
// Index `tableName → projection-name` from the registry, used by the
|
|
2
|
+
// app-side projection rebuild (`kumiko schema apply` reads rebuild markers,
|
|
3
|
+
// maps tables to projections, then calls rebuildProjection).
|
|
4
4
|
//
|
|
5
|
-
// Drizzle-
|
|
6
|
-
// buildEntityTable/deriveEntityTableMeta
|
|
5
|
+
// Drizzle-free: the table name comes from the kumiko symbol attached to the
|
|
6
|
+
// table definition by buildEntityTable/deriveEntityTableMeta.
|
|
7
7
|
import { extractTableName } from "../db/index.js";
|
|
8
|
-
/** Index `tableName → projection-name`
|
|
9
|
-
* table
|
|
10
|
-
* Side-effect-only MSPs (table omitted)
|
|
8
|
+
/** Index `tableName → projection-name` from the registry. Only projections with
|
|
9
|
+
* a table definition (single-stream + multi-stream-with-table) are included.
|
|
10
|
+
* Side-effect-only MSPs (table omitted) cannot be rebuilt. */
|
|
11
11
|
export function buildProjectionTableIndex(registry) {
|
|
12
12
|
const index = new Map();
|
|
13
13
|
for (const [name, def] of registry.getAllProjections()) {
|
|
@@ -52,7 +52,7 @@ export function validateMetricName(name, type) {
|
|
|
52
52
|
// time) — normalize "-" to "_" here so a feature like "ai-foundation"
|
|
53
53
|
// resolves to the same "kumiko_ai_foundation_x" on both the registration
|
|
54
54
|
// path (registry-ingest.ts) and the read path (ctx.metrics / ctx.metricsFor),
|
|
55
|
-
// instead of the kebab form being rejected outright
|
|
55
|
+
// instead of the kebab form being rejected outright.
|
|
56
56
|
export function buildMetricName(featureName, shortName) {
|
|
57
57
|
const normalizedFeatureName = toKebab(featureName).replace(/-/g, "_");
|
|
58
58
|
if (!SNAKE_CASE.test(normalizedFeatureName)) {
|
|
@@ -25,12 +25,12 @@ export function createMetricsHandle(meter, featureName) {
|
|
|
25
25
|
}
|
|
26
26
|
// Same feature-bound resolution as createMetricsHandle, but for an
|
|
27
27
|
// explicit `featureName` chosen by the caller rather than the dispatching
|
|
28
|
-
// handler's own feature
|
|
28
|
+
// handler's own feature. Meant for shared/library code
|
|
29
29
|
// invoked from many features' HandlerContext (ctx.metricsFor) — the
|
|
30
30
|
// library owns one stable metric name instead of splintering into
|
|
31
31
|
// kumiko_<caller>_x per consumer.
|
|
32
32
|
//
|
|
33
|
-
// Decision
|
|
33
|
+
// Decision: unlike createMetricsHandle, an
|
|
34
34
|
// unregistered name here is a silent no-op, not a throw. This handle is
|
|
35
35
|
// meant for error/catch-path counters in shared code — a missing
|
|
36
36
|
// registration (consuming feature not mounted, metric not declared yet)
|
|
@@ -101,7 +101,7 @@ export function createNoopMetricsHandle() {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
// Shared factory behind ctx.metricsFor for handler and job contexts, so both
|
|
104
|
-
// keep the same no-op-on-unregistered contract
|
|
104
|
+
// keep the same no-op-on-unregistered contract.
|
|
105
105
|
export function createMetricsForFactory(meter) {
|
|
106
106
|
return meter
|
|
107
107
|
? (featureName) => createSafeMetricsHandle(meter, featureName)
|