@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,3 +1,5 @@
|
|
|
1
|
+
import { createFallbackLogger } from "../logging/utils.js";
|
|
2
|
+
const log = createFallbackLogger("kumiko");
|
|
1
3
|
// Entity name the renderer gives the synthesized entity of an actionForm, secretMint or confirm
|
|
2
4
|
// step. It is not registered, so nothing server-side may treat it as a real entity.
|
|
3
5
|
export const ACTION_FORM_PSEUDO_ENTITY = "__action-form__";
|
|
@@ -71,7 +73,7 @@ export function isWriteFormEditSection(section) {
|
|
|
71
73
|
}
|
|
72
74
|
// Union of both sources, unlike the boot-validator's either-or flattening: that
|
|
73
75
|
// one runs after the fields-XOR-groups check, collectors run without it and must
|
|
74
|
-
// not drop a source
|
|
76
|
+
// not drop a source.
|
|
75
77
|
export function sectionFieldSpecs(section) {
|
|
76
78
|
return [...(section.fields ?? []), ...(section.groups?.flatMap((group) => group.fields) ?? [])];
|
|
77
79
|
}
|
|
@@ -90,8 +92,7 @@ export function normalizeListColumn(c) {
|
|
|
90
92
|
process.env.NODE_ENV !== "production" &&
|
|
91
93
|
col.renderer !== undefined &&
|
|
92
94
|
typeof col.renderer === "function") {
|
|
93
|
-
|
|
94
|
-
console.warn(`[kumiko] normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`);
|
|
95
|
+
log.warn(`normalizeListColumn: Feld "${col.field}" hat einen Funktions-Renderer — dieser wird von JSON.stringify verworfen. Bitte auf FormatSpec ({ format: "..." }) migrieren.`);
|
|
95
96
|
}
|
|
96
97
|
return col;
|
|
97
98
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type-safe transition graph. Wraps the underlying Map so callers don't
|
|
3
3
|
* accidentally fall into the `transitions[from]?.includes(to)` footgun
|
|
4
|
-
* (
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* (indexing a Map like an object returns undefined and silently breaks the
|
|
5
|
+
* entire check). The API therefore enforces explicit methods, with no Map
|
|
6
|
+
* shape exposed externally.
|
|
7
7
|
*
|
|
8
8
|
* - `canTransition(from, to)` → boolean
|
|
9
|
-
* - `allowedFrom(from)` → readonly string[] (
|
|
10
|
-
*
|
|
11
|
-
* - `assertTransition(from, to)` →
|
|
9
|
+
* - `allowedFrom(from)` → readonly string[] (empty when `from` is unknown
|
|
10
|
+
* or terminal)
|
|
11
|
+
* - `assertTransition(from, to)` → throws UnprocessableError(invalid_transition)
|
|
12
12
|
*/
|
|
13
13
|
export type TransitionGraph<TStates extends string = string> = {
|
|
14
14
|
readonly canTransition: (from: TStates, to: TStates) => boolean;
|
|
@@ -17,8 +17,8 @@ export type TransitionGraph<TStates extends string = string> = {
|
|
|
17
17
|
};
|
|
18
18
|
/**
|
|
19
19
|
* Defines allowed state transitions. Returns a typed graph (see
|
|
20
|
-
* TransitionGraph)
|
|
21
|
-
* type
|
|
20
|
+
* TransitionGraph), not a Map. This makes `transitions[x]` access a
|
|
21
|
+
* type error instead of silently returning undefined.
|
|
22
22
|
*/
|
|
23
23
|
export declare function defineTransitions<const TMap extends Record<string, readonly string[]>>(map: TMap): TransitionGraph<keyof TMap & string>;
|
|
24
24
|
/**
|
|
@@ -26,9 +26,9 @@ export declare function defineTransitions<const TMap extends Record<string, read
|
|
|
26
26
|
* reason="invalid_transition" if not — the 422 status lets the client
|
|
27
27
|
* distinguish a logical rejection from a validation or auth failure.
|
|
28
28
|
*
|
|
29
|
-
* Convenience
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
29
|
+
* Convenience wrapper around `transitions.assertTransition(from, to)` —
|
|
30
|
+
* it exists because existing callers use the `guardTransition(graph, ...)`
|
|
31
|
+
* form. Both are supported; the graph method is the idiomatic API for new
|
|
32
|
+
* code.
|
|
33
33
|
*/
|
|
34
34
|
export declare function guardTransition<TStates extends string>(transitions: TransitionGraph<TStates>, from: TStates, to: TStates): void;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { buildInvalidTransitionDetails, FrameworkReasons, UnprocessableError, } from "../errors/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Defines allowed state transitions. Returns a typed graph (see
|
|
4
|
-
* TransitionGraph)
|
|
5
|
-
* type
|
|
4
|
+
* TransitionGraph), not a Map. This makes `transitions[x]` access a
|
|
5
|
+
* type error instead of silently returning undefined.
|
|
6
6
|
*/
|
|
7
7
|
export function defineTransitions(map) {
|
|
8
8
|
const internal = new Map();
|
|
@@ -17,7 +17,7 @@ export function defineTransitions(map) {
|
|
|
17
17
|
},
|
|
18
18
|
assertTransition: (from, to) => {
|
|
19
19
|
const set = internal.get(from);
|
|
20
|
-
// skip:
|
|
20
|
+
// skip: allowed transition — no throw; fall through.
|
|
21
21
|
if (set?.has(to) === true)
|
|
22
22
|
return;
|
|
23
23
|
const allowed = set ? [...set] : [];
|
|
@@ -33,10 +33,10 @@ export function defineTransitions(map) {
|
|
|
33
33
|
* reason="invalid_transition" if not — the 422 status lets the client
|
|
34
34
|
* distinguish a logical rejection from a validation or auth failure.
|
|
35
35
|
*
|
|
36
|
-
* Convenience
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
36
|
+
* Convenience wrapper around `transitions.assertTransition(from, to)` —
|
|
37
|
+
* it exists because existing callers use the `guardTransition(graph, ...)`
|
|
38
|
+
* form. Both are supported; the graph method is the idiomatic API for new
|
|
39
|
+
* code.
|
|
40
40
|
*/
|
|
41
41
|
// @wrapper-known semantic-alias
|
|
42
42
|
export function guardTransition(transitions, from, to) {
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
// was needed to satisfy TS narrowing on a property-access; passing the
|
|
14
14
|
// arg through a function-call achieves the same narrowing trivially via
|
|
15
15
|
// the function-parameter binding.
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
function isResolverFunction(arg) {
|
|
17
|
+
return typeof arg === "function";
|
|
18
|
+
}
|
|
18
19
|
/**
|
|
19
20
|
* Resolve a required StepResolver — either a static value or a function.
|
|
20
21
|
* Throws if `arg` is undefined; caller must guarantee presence.
|
|
21
22
|
*/
|
|
22
23
|
export function resolveRequired(arg, ctx) {
|
|
23
|
-
if (
|
|
24
|
+
if (isResolverFunction(arg)) {
|
|
24
25
|
return arg(ctx);
|
|
25
26
|
}
|
|
26
27
|
return arg;
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
// ```
|
|
25
25
|
//
|
|
26
26
|
// M.1.6 supports `concurrency: 1` only (sequential). Concurrent
|
|
27
|
-
// execution (Promise.all-style)
|
|
27
|
+
// execution (Promise.all-style) requires its own audit cycle: TX-sharing,
|
|
28
28
|
// AbortSignal-cancellation, and lifecycle-hook ordering each warrant
|
|
29
|
-
//
|
|
29
|
+
// separate review.
|
|
30
30
|
//
|
|
31
|
-
//
|
|
32
|
-
// from the sub-pipeline's resolvers.
|
|
31
|
+
// `as` is required — without it, the current item is unreachable
|
|
32
|
+
// from the sub-pipeline's resolvers. `r.step.return` inside `do` is
|
|
33
33
|
// rejected at build time (would trigger the discriminated-union
|
|
34
34
|
// TData-Inference trap).
|
|
35
35
|
import { defineStep } from "../define-step.js";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// r.requires.projection allowlist, must NOT be a registered
|
|
7
7
|
// r.entity-aggregate-table. Skips the same set of framework-protections
|
|
8
8
|
// (lifecycle hooks, field-access, audit-trail, etc.) — see
|
|
9
|
-
// step-vocabulary.md "
|
|
9
|
+
// step-vocabulary.md "What unsafeProjection.* skips".
|
|
10
10
|
//
|
|
11
11
|
// Convention (not enforced): most legitimate read-side deletions are
|
|
12
12
|
// downstream of an aggregate event (e.g. delete-user → cascading
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Idempotent on the supplied conflict-key columns (typically the
|
|
4
4
|
// natural key + tenantId). Skips lifecycle hooks, field-access,
|
|
5
|
-
// crypto-shredding, schema-versioning, audit-trail, read-access-log.
|
|
6
|
-
// See "
|
|
5
|
+
// crypto-shredding, schema-versioning, audit-trail, and read-access-log.
|
|
6
|
+
// See "What unsafeProjection.* skips" in
|
|
7
7
|
// kumiko-platform/docs/archive/plans/architecture/intern/step-vocabulary.md.
|
|
8
8
|
//
|
|
9
9
|
// Use only on tables explicitly declared via r.requires.projection in
|
|
10
|
-
// the owning feature. Aggregate
|
|
11
|
-
// rejected by boot
|
|
10
|
+
// the owning feature. Aggregate tables (registered via r.entity) are
|
|
11
|
+
// rejected by boot validation — domain mutation MUST go through
|
|
12
12
|
// r.step.aggregate.*.
|
|
13
13
|
import { extractTableName } from "../../db/index.js";
|
|
14
14
|
import { executeRawQuery } from "../../db/queries/raw-sql.js";
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
// resultKey is `args.event` itself (the branded event-type string) rather
|
|
17
17
|
// than a new `name` argument — a workflow only ever has one `awaits` entry
|
|
18
18
|
// per event type, so `ctx.steps[awaits.someKey]` is already a unique,
|
|
19
|
-
// intention-revealing handle. resume-run.write.ts
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// intention-revealing handle. resume-run.write.ts is the only producer of
|
|
20
|
+
// this key's value: the step itself always suspends (never returns a real
|
|
21
|
+
// result), so a subsequent step only ever sees the matched event's payload
|
|
22
|
+
// after a resume seeds it in.
|
|
23
23
|
import { defineStep } from "../define-step.js";
|
|
24
24
|
import { addDuration } from "./_duration-utils.js";
|
|
25
25
|
import { resolveOptional, resolveRequired } from "./_resolver-utils.js";
|
|
@@ -15,7 +15,7 @@ const triggerEventRefSchema = z.object({
|
|
|
15
15
|
aggregateId: z.string(),
|
|
16
16
|
version: z.number().int(),
|
|
17
17
|
});
|
|
18
|
-
// Optional on every step event: runs started before the
|
|
18
|
+
// Optional on every step event: runs started before the fingerprint existed
|
|
19
19
|
// have none.
|
|
20
20
|
const stepContext = {
|
|
21
21
|
workflowName: z.string(),
|
|
@@ -29,8 +29,8 @@ const WORKFLOW_SYSTEM_EVENT_SCHEMAS = [
|
|
|
29
29
|
z.object({
|
|
30
30
|
workflowName: z.string(),
|
|
31
31
|
triggerEventType: z.string(),
|
|
32
|
-
// Current streams reference the trigger event; streams
|
|
33
|
-
//
|
|
32
|
+
// Current streams reference the trigger event; older streams carry a copy of
|
|
33
|
+
// its payload instead.
|
|
34
34
|
triggerEventRef: triggerEventRefSchema.optional(),
|
|
35
35
|
triggerPayload: z.unknown().optional(),
|
|
36
36
|
definitionFingerprint: z.string().optional(),
|
|
@@ -4,30 +4,32 @@ import type { RegistrarExtensionRegistration } from "./types/config.ts";
|
|
|
4
4
|
import type { FeatureDefinition, Registry } from "./types/feature.ts";
|
|
5
5
|
import type { TenantId } from "./types/identifiers.ts";
|
|
6
6
|
/**
|
|
7
|
-
* Extension
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* Extension name under which a tier-resolver plugin must be registered in the
|
|
8
|
+
* registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
|
|
9
|
+
* and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
|
|
10
|
+
* on the same value, preventing typos.
|
|
11
11
|
*/
|
|
12
12
|
export declare const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
|
|
13
13
|
/**
|
|
14
|
-
* Resolver
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Resolver callback shape: synchronous (on the dispatcher's hot path), per
|
|
15
|
+
* tenant. Returns the tenant's effective feature set. Implementations should
|
|
16
|
+
* maintain an in-memory cache and invalidate it via `r.entityHook`.
|
|
17
17
|
*
|
|
18
|
-
* **System-context convention:**
|
|
19
|
-
*
|
|
18
|
+
* **System-context convention:** calls with SYSTEM_TENANT_ID must return the
|
|
19
|
+
* union of all tier features (see the DispatcherOptions.effectiveFeatures
|
|
20
|
+
* doc block).
|
|
20
21
|
*/
|
|
21
22
|
export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<string>) & {
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
24
|
+
* Optional live-trial gate, consulted by the dispatcher feature gate ONLY
|
|
25
|
+
* when the synchronous resolver reports a feature as disabled. Reads the
|
|
26
|
+
* tenant's signup date live (tenant.inserted_at) and returns true when the
|
|
27
|
+
* tenant is within the trial window AND the feature belongs to the trial
|
|
28
|
+
* tier. It is async and not part of the boot cache because the trial is
|
|
29
|
+
* time-dependent and can change between requests. Consulted at three
|
|
30
|
+
* locations: the two dispatch-gate call sites (checkFeatureEnabled) AND
|
|
31
|
+
* ctx.hasFeature in the handler body (as a fallback when the synchronous
|
|
32
|
+
* feature-set check is negative).
|
|
31
33
|
*/
|
|
32
34
|
readonly trialGate?: TrialGate;
|
|
33
35
|
};
|
|
@@ -36,10 +38,10 @@ export type EffectiveFeaturesResolver = ((tenantId: TenantId) => ReadonlySet<str
|
|
|
36
38
|
*/
|
|
37
39
|
export type TrialGate = (tenantId: TenantId, featureName: string) => Promise<boolean>;
|
|
38
40
|
/**
|
|
39
|
-
* Plugin
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* Plugin shape for the tier-resolver extension. Plugins implement `build`
|
|
42
|
+
* as a boot-time factory: they receive `db` + `registry` (after stack setup),
|
|
43
|
+
* load the initial cache from the database, and return the synchronous
|
|
44
|
+
* resolver callback, optionally with an attached `trialGate`.
|
|
43
45
|
*/
|
|
44
46
|
export type TierResolverPlugin = {
|
|
45
47
|
readonly build: (deps: {
|
|
@@ -49,13 +51,13 @@ export type TierResolverPlugin = {
|
|
|
49
51
|
}) => Promise<EffectiveFeaturesResolver>;
|
|
50
52
|
};
|
|
51
53
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* Scans a composed feature list for a `tenantTierResolver` extension usage.
|
|
55
|
+
* Only one plugin is supported because multiple plugins would be ambiguous:
|
|
56
|
+
* it would be unclear which resolver should win. No multi-merge pattern is
|
|
57
|
+
* needed until there is a real use case.
|
|
56
58
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
+
* Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
|
|
60
|
+
* identical and cannot drift.
|
|
59
61
|
*/
|
|
60
62
|
/** Narrows a registration's untyped `options` bag to the tier-resolver
|
|
61
63
|
* plugin shape — the only thing every implementation must provide is the
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Tier composition: framework extension point for per-tenant
|
|
2
2
|
// effective-feature resolution.
|
|
3
3
|
//
|
|
4
|
-
// **Pattern (
|
|
5
|
-
// declares `r.extendsRegistrar("tenantTierResolver")
|
|
6
|
-
//
|
|
4
|
+
// **Pattern (analogous to mail-foundation / file-foundation):** a feature
|
|
5
|
+
// declares `r.extendsRegistrar("tenantTierResolver")`, and plugins
|
|
6
|
+
// implement it via `r.useExtension("tenantTierResolver", "id", { build })`.
|
|
7
7
|
//
|
|
8
|
-
// **
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
8
|
+
// **Framework auto-wiring:** runDevApp + runProdApp scan the registry
|
|
9
|
+
// for this exact extension name. If a plugin is found AND the app author
|
|
10
|
+
// has not set an `effectiveFeatures` callback, `plugin.build({ db, registry })`
|
|
11
|
+
// is called once in onAfterSetup. The returned callback becomes
|
|
12
|
+
// `effectiveFeatures` in the dispatcher.
|
|
13
13
|
//
|
|
14
|
-
// **
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
14
|
+
// **Why this pattern:** the app author mounts
|
|
15
|
+
// `createTierEngineFeature(opts)` and is done—no late-bound holder in the
|
|
16
|
+
// run config and no need to wire an effectiveFeatures callback. The framework
|
|
17
|
+
// handles the late-binding internally. Tier composition is a feature, not a
|
|
18
|
+
// subsystem with app-specific wiring.
|
|
19
19
|
//
|
|
20
|
-
// **Apps
|
|
21
|
-
//
|
|
20
|
+
// **Apps without a tier engine:** if no plugin is registered, the framework
|
|
21
|
+
// does nothing—`effectiveFeatures` remains undefined, and all features are on.
|
|
22
22
|
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
23
23
|
/**
|
|
24
|
-
* Extension
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Extension name under which a tier-resolver plugin must be registered in the
|
|
25
|
+
* registry. The constant keeps `r.useExtension(TENANT_TIER_RESOLVER_EXT, ...)`
|
|
26
|
+
* and the framework's `getExtensionUsages(TENANT_TIER_RESOLVER_EXT)` aligned
|
|
27
|
+
* on the same value, preventing typos.
|
|
28
28
|
*/
|
|
29
29
|
export const TENANT_TIER_RESOLVER_EXT = "tenantTierResolver";
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* Scans a composed feature list for a `tenantTierResolver` extension usage.
|
|
32
|
+
* Only one plugin is supported because multiple plugins would be ambiguous:
|
|
33
|
+
* it would be unclear which resolver should win. No multi-merge pattern is
|
|
34
|
+
* needed until there is a real use case.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* Shared by runDevApp + runProdApp so the pickup path remains byte-for-byte
|
|
37
|
+
* identical and cannot drift.
|
|
38
38
|
*/
|
|
39
39
|
/** Narrows a registration's untyped `options` bag to the tier-resolver
|
|
40
40
|
* plugin shape — the only thing every implementation must provide is the
|
|
@@ -61,7 +61,7 @@ function parentRefEdgesFrom(entityName, entity, allEntityNames) {
|
|
|
61
61
|
//
|
|
62
62
|
// A type can legitimately be reached by more than one edge — `note.vehicleId`
|
|
63
63
|
// and `note.campaignId` are distinct, and collapsing them by entity name would
|
|
64
|
-
// leave one edge's rows behind,
|
|
64
|
+
// leave one edge's rows behind, causing a silent partial move.
|
|
65
65
|
export function resolveTransferAdjacency(entities, rootEntityType) {
|
|
66
66
|
const allEntityNames = [...entities.keys()];
|
|
67
67
|
const byParent = new Map();
|
|
@@ -96,7 +96,7 @@ export function findOverDeepTransferChain(adjacency, onPath) {
|
|
|
96
96
|
// skip: a cycle revisits a type already on this path, and schema depth
|
|
97
97
|
// cannot measure how far it actually runs — that depends on the rows, not
|
|
98
98
|
// the declaration. The mover carries this one instead, failing with
|
|
99
|
-
// `transfer_graph_too_deep` when its rounds run out
|
|
99
|
+
// `transfer_graph_too_deep` when its rounds run out.
|
|
100
100
|
if (onPath.includes(edge.entityName))
|
|
101
101
|
continue;
|
|
102
102
|
const deeper = findOverDeepTransferChain(adjacency, [...onPath, edge.entityName]);
|
|
@@ -23,35 +23,16 @@
|
|
|
23
23
|
// caught by this validator. A future lint-rule will enforce the contract
|
|
24
24
|
// statically; today it lives in this comment + the StepBuilder doc.
|
|
25
25
|
import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
26
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
26
27
|
import { schemaTableName } from "../db/table-introspection.js";
|
|
27
28
|
import { getStep } from "./define-step.js";
|
|
28
29
|
import { buildPipelineSteps } from "./pipeline.js";
|
|
30
|
+
import { walkStepInstances } from "./walk-step-instances.js";
|
|
29
31
|
// Listed step-kinds whose `args.table` must be in the owning feature's
|
|
30
32
|
// r.requires.projection allowlist. Extend as further unsafeProjection.*
|
|
31
33
|
// steps land — don't pre-list hypothetical kinds (CLAUDE.md: don't design
|
|
32
34
|
// for scenarios that can't happen).
|
|
33
35
|
const UNSAFE_PROJECTION_KINDS = new Set(["unsafeProjectionUpsert", "unsafeProjectionDelete"]);
|
|
34
|
-
// Sub-pipeline arg-paths come from `defineStep({ subPaths: [...] })` —
|
|
35
|
-
// each builder declares its own (e.g. branch's onTrue/onFalse, forEach's
|
|
36
|
-
// do). Walking via the registry means nested unsafeProjection-* in NEW
|
|
37
|
-
// sub-step-builders is automatically caught the moment the builder
|
|
38
|
-
// registers itself; no central map to keep in sync. Followup #15.
|
|
39
|
-
function* walkAllSteps(steps) {
|
|
40
|
-
for (const step of steps) {
|
|
41
|
-
yield step;
|
|
42
|
-
const def = getStep(step.kind);
|
|
43
|
-
const subPaths = def?.subPaths;
|
|
44
|
-
if (!subPaths || subPaths.length === 0)
|
|
45
|
-
continue;
|
|
46
|
-
const args = step.args;
|
|
47
|
-
for (const path of subPaths) {
|
|
48
|
-
const subSteps = args[path];
|
|
49
|
-
if (Array.isArray(subSteps)) {
|
|
50
|
-
yield* walkAllSteps(subSteps);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
36
|
// table()/buildEntityTable spread column handles as enumerable props, so an
|
|
56
37
|
// entity field named `tableName`/`source` would shadow the matching meta key —
|
|
57
38
|
// the canonical meta under KUMIKO_META_SYMBOL is the only collision-safe source.
|
|
@@ -95,7 +76,6 @@ export function validateProjectionAllowlist(features) {
|
|
|
95
76
|
// the first feature's projections (and vice-versa). Detect and surface
|
|
96
77
|
// here so the error names BOTH owners; without this, the silent-set
|
|
97
78
|
// would point any later unsafeProjection-error at the wrong feature.
|
|
98
|
-
// Followup #8.
|
|
99
79
|
const aggregateTables = new Map();
|
|
100
80
|
for (const f of features) {
|
|
101
81
|
for (const [entityName, entity] of Object.entries(f.entities ?? {})) {
|
|
@@ -129,8 +109,8 @@ export function validateProjectionAllowlist(features) {
|
|
|
129
109
|
`Closure body must produce the step list without reading event-payload fields ` +
|
|
130
110
|
`(those belong inside step resolvers).`);
|
|
131
111
|
}
|
|
132
|
-
for (const step of
|
|
133
|
-
// Tier-2 step-discovery
|
|
112
|
+
for (const step of walkStepInstances(steps, `[Feature ${f.name}] writeHandler "${handlerName}"`)) {
|
|
113
|
+
// Tier-2 step-discovery: require explicit opt-in via
|
|
134
114
|
// r.requires.step("<kind>") for any tier-2+ step. Tier-1 is implicit.
|
|
135
115
|
const def = getStep(step.kind);
|
|
136
116
|
if (def?.tier === 2 && !f.requiredSteps.has(step.kind)) {
|
|
@@ -140,12 +120,12 @@ export function validateProjectionAllowlist(features) {
|
|
|
140
120
|
}
|
|
141
121
|
if (!UNSAFE_PROJECTION_KINDS.has(step.kind))
|
|
142
122
|
continue;
|
|
143
|
-
const
|
|
144
|
-
if (!
|
|
123
|
+
const stepTable = isPlainObject(step.args) ? step.args["table"] : undefined;
|
|
124
|
+
if (!stepTable) {
|
|
145
125
|
throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" has a ${step.kind} step ` +
|
|
146
126
|
`without a \`table\` argument.`);
|
|
147
127
|
}
|
|
148
|
-
const tableName = resolveTableNameFromStep(
|
|
128
|
+
const tableName = resolveTableNameFromStep(stepTable);
|
|
149
129
|
const aggregateOwner = aggregateTables.get(tableName);
|
|
150
130
|
if (aggregateOwner) {
|
|
151
131
|
throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" uses ${step.kind} on table "${tableName}", ` +
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
+
import { getStep } from "./define-step.js";
|
|
3
|
+
function isStepInstance(value) {
|
|
4
|
+
return isPlainObject(value) && typeof value["kind"] === "string";
|
|
5
|
+
}
|
|
6
|
+
// Sub-pipeline arg-paths come from `defineStep({ subPaths })`, so nested steps of new
|
|
7
|
+
// sub-step builders are walked without a central map.
|
|
8
|
+
// Fail-closed: an entry the walker cannot read would hide a step from the caller's check
|
|
9
|
+
// (projection allowlist, event-trigger support), so malformed input throws instead of being skipped.
|
|
10
|
+
export function* walkStepInstances(steps, where) {
|
|
11
|
+
for (const [stepIndex, step] of steps.entries()) {
|
|
12
|
+
yield step;
|
|
13
|
+
const subPaths = getStep(step.kind)?.subPaths;
|
|
14
|
+
if (!subPaths || subPaths.length === 0)
|
|
15
|
+
continue;
|
|
16
|
+
const stepWhere = `${where} step[${stepIndex}] "${step.kind}"`;
|
|
17
|
+
if (!isPlainObject(step.args)) {
|
|
18
|
+
throw new Error(`${stepWhere} declares sub-paths (${subPaths.join(", ")}) but its args is not an object.`);
|
|
19
|
+
}
|
|
20
|
+
for (const path of subPaths) {
|
|
21
|
+
const subSteps = step.args[path];
|
|
22
|
+
// skip: optional sub-path not provided (branch.run treats onFalse: null like absent)
|
|
23
|
+
if (subSteps === undefined || subSteps === null)
|
|
24
|
+
continue;
|
|
25
|
+
if (!Array.isArray(subSteps)) {
|
|
26
|
+
throw new Error(`${stepWhere} sub-path "${path}" must be a list of steps.`);
|
|
27
|
+
}
|
|
28
|
+
const checked = [];
|
|
29
|
+
for (const [entryIndex, entry] of subSteps.entries()) {
|
|
30
|
+
if (!isStepInstance(entry)) {
|
|
31
|
+
throw new Error(`${stepWhere} sub-path "${path}"[${entryIndex}] is not a step (expected an object with a string "kind").`);
|
|
32
|
+
}
|
|
33
|
+
checked.push(entry);
|
|
34
|
+
}
|
|
35
|
+
yield* walkStepInstances(checked, `${stepWhere} ${path}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -6,7 +6,7 @@ import { toKebab } from "./qualified-name.js";
|
|
|
6
6
|
export const WRITE_HANDLER_QN_FORMAT_RE = /^[a-zA-Z][a-zA-Z0-9-]*:write:[a-zA-Z][a-zA-Z0-9-]*(:[a-zA-Z][a-zA-Z0-9-]*)*$/;
|
|
7
7
|
// Matches `dispatcher.write("qn", ...)` and `<expr>.write("qn", ...)` —
|
|
8
8
|
// same surface the CI guard scans. Dynamic QNs (variables, templates) are
|
|
9
|
-
// intentionally skipped (known limitation
|
|
9
|
+
// intentionally skipped (known limitation).
|
|
10
10
|
const DISPATCHER_WRITE_LITERAL_RE = /\.write\s*\(\s*["']([^"']+)["']/g;
|
|
11
11
|
/** Extract string-literal write-handler QNs from TS/TSX source text. */
|
|
12
12
|
export function extractDispatcherWriteQnsFromSource(source) {
|
package/dist/entrypoint/index.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
//
|
|
37
37
|
// SSE fanout across processes (split api/worker deploy, replicas > 1):
|
|
38
38
|
// buildServer's default sseBroker is Redis-backed whenever REDIS_URL is
|
|
39
|
-
// set
|
|
39
|
+
// set — a push on the worker's dispatcher reaches clients
|
|
40
40
|
// connected to any API instance via Redis Pub/Sub, no per-instance
|
|
41
41
|
// cursor needed. Without REDIS_URL, the in-memory broker still only
|
|
42
42
|
// reaches clients on the same process.
|
|
@@ -55,7 +55,7 @@ import { resolveCacheSyncBus } from "../redis/cache-sync-bus.js";
|
|
|
55
55
|
// so the command-dispatcher fires handleEvent as an afterCommit-hook for
|
|
56
56
|
// event-triggered jobs (dispatcher.ts:997). Without this plumbing,
|
|
57
57
|
// `r.job({ trigger: { on: … } })` silently drops on every write — that
|
|
58
|
-
// was the hidden
|
|
58
|
+
// was the hidden gap.
|
|
59
59
|
//
|
|
60
60
|
// Caller-supplied `dispatcherOptions.jobRunner` wins (tests sometimes
|
|
61
61
|
// inject a mock runner directly).
|
|
@@ -198,7 +198,7 @@ export function createApiEntrypoint(options) {
|
|
|
198
198
|
const lifecycle = options.lifecycle ?? createLifecycle({ startReady: true });
|
|
199
199
|
const observability = resolveObservability(options.observability);
|
|
200
200
|
const { options: busOptions, context } = resolveEntrypointCacheSync(options, lifecycle);
|
|
201
|
-
// Boot-validation
|
|
201
|
+
// Boot-validation — fail loud before traffic arrives:
|
|
202
202
|
// (a) Any jobs declared + no jobs-block → command-dispatcher would
|
|
203
203
|
// silently drop every event-triggered enqueue. Fix: add jobs:
|
|
204
204
|
// { redisUrl } so the API holds lane-queue-clients.
|
package/dist/env/index.d.ts
CHANGED
|
@@ -116,12 +116,13 @@ export type ParseEnvOptions = {
|
|
|
116
116
|
* is `undefined` in the result despite its `string` type — it is only set once
|
|
117
117
|
* the boot-time decrypt (resolvePlatformKeks) has run. */
|
|
118
118
|
export declare function parseEnv<S extends z.ZodObject<z.ZodRawShape>>(schema: S, env: Record<string, string | undefined>, options?: ParseEnvOptions): z.infer<S>;
|
|
119
|
-
/** Dry-
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
119
|
+
/** Dry-run counterpart to parseEnv (KUMIKO_DRY_RUN_ENV path): parses each
|
|
120
|
+
* field individually and returns an honest Partial — present values pass
|
|
121
|
+
* through with their types (coerced), while missing or invalid values are
|
|
122
|
+
* simply omitted. Never throws: the dry run renders the inventory instead
|
|
123
|
+
* of failing on required fields. Replaces the `({} as Shape)` pattern, where
|
|
124
|
+
* every access silently returned undefined and the compiler could not detect
|
|
125
|
+
* it. */
|
|
125
126
|
export declare function parseEnvDryRun<S extends z.ZodObject<z.ZodRawShape>>(schema: S, env: Record<string, string | undefined>): Partial<z.infer<S>>;
|
|
126
127
|
export type EnvFieldStatus = "set" | "unset" | "missing" | "invalid";
|
|
127
128
|
export type EnvFieldInspection = {
|
package/dist/env/index.js
CHANGED
|
@@ -209,7 +209,7 @@ export function composeEnvSchema(options) {
|
|
|
209
209
|
// rather than silently overwriting a deploy-critical default. The
|
|
210
210
|
// conflict-detection makes the order safe in both directions — both
|
|
211
211
|
// throw — but downstream tools that consume `sources` (e.g. the
|
|
212
|
-
// upcoming
|
|
212
|
+
// upcoming lint-guard) may rely on this insertion order.
|
|
213
213
|
if (options.core) {
|
|
214
214
|
for (const [key, field] of Object.entries(zodShape(options.core))) {
|
|
215
215
|
merged[key] = field;
|
|
@@ -356,12 +356,13 @@ export function parseEnv(schema, env, options = {}) {
|
|
|
356
356
|
function ucfirst(s) {
|
|
357
357
|
return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1);
|
|
358
358
|
}
|
|
359
|
-
/** Dry-
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
359
|
+
/** Dry-run counterpart to parseEnv (KUMIKO_DRY_RUN_ENV path): parses each
|
|
360
|
+
* field individually and returns an honest Partial — present values pass
|
|
361
|
+
* through with their types (coerced), while missing or invalid values are
|
|
362
|
+
* simply omitted. Never throws: the dry run renders the inventory instead
|
|
363
|
+
* of failing on required fields. Replaces the `({} as Shape)` pattern, where
|
|
364
|
+
* every access silently returned undefined and the compiler could not detect
|
|
365
|
+
* it. */
|
|
365
366
|
export function parseEnvDryRun(schema, env) {
|
|
366
367
|
const out = {};
|
|
367
368
|
for (const [name, field] of Object.entries(zodShape(schema))) {
|
|
@@ -372,8 +373,8 @@ export function parseEnvDryRun(schema, env) {
|
|
|
372
373
|
if (result.success)
|
|
373
374
|
out[name] = result.data;
|
|
374
375
|
}
|
|
375
|
-
// @cast-boundary schema-walk — Partial<z.infer<S>> erasure
|
|
376
|
-
// per-
|
|
376
|
+
// @cast-boundary schema-walk — Partial<z.infer<S>> erasure through the
|
|
377
|
+
// per-field safeParse; every included value passed its field-level parse.
|
|
377
378
|
return out;
|
|
378
379
|
}
|
|
379
380
|
/** Checks every field of `schema` on its own against `env`, for the boot dry
|