@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
|
@@ -16,10 +16,11 @@ export function isRepresentableAtScale(n, scale) {
|
|
|
16
16
|
const tolerance = Math.abs(scaled) * 8 * Number.EPSILON + Number.EPSILON;
|
|
17
17
|
return Math.abs(scaled - Math.round(scaled)) <= tolerance;
|
|
18
18
|
}
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// Lexicographic ISO comparison — exact for `yyyy-mm-dd` (date) and correct
|
|
20
|
+
// for ISO datetimes in a consistent representation (same offset/precision
|
|
21
|
+
// form). Deliberately avoids the Date API (no-date-api-Guard); day-level
|
|
22
|
+
// comparison is sufficient for min/max use cases (e.g. a birth date not in
|
|
23
|
+
// the future).
|
|
23
24
|
function withDateBounds(schema, min, max) {
|
|
24
25
|
if (min === undefined && max === undefined)
|
|
25
26
|
return schema;
|
|
@@ -127,8 +128,9 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
127
128
|
return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
|
|
128
129
|
}
|
|
129
130
|
case "longText": {
|
|
130
|
-
// longText
|
|
131
|
-
//
|
|
131
|
+
// longText has no `format` variant (by type design). Only optional
|
|
132
|
+
// maxLength and required are supported; otherwise it is an unrestricted
|
|
133
|
+
// z.string().
|
|
132
134
|
let schema = z.string();
|
|
133
135
|
if (field.maxLength)
|
|
134
136
|
schema = schema.max(field.maxLength);
|
|
@@ -148,13 +150,13 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
148
150
|
if (field.default !== undefined) {
|
|
149
151
|
// Untouched <select> sends "" too; with a default that maps to the
|
|
150
152
|
// default (same semantics as undefined) instead of the invalid-value
|
|
151
|
-
// rejection
|
|
153
|
+
// rejection. A field with a default is never "unset" —
|
|
152
154
|
// true on both insert AND update, so this branch (and its "" → default
|
|
153
155
|
// mapping) fires regardless of applyDefaults; only the `.default(...)`
|
|
154
156
|
// schema-level fallback for OMITTED input is update-gated below.
|
|
155
157
|
// `null` maps the same way: the no-default branch below normalizes
|
|
156
158
|
// an untouched select to null, and a client that reuses that value
|
|
157
|
-
// against a
|
|
159
|
+
// against a defaulted field must not get rejected either.
|
|
158
160
|
const mapped = z.preprocess((value) => (value === "" || value === null ? field.default : value), enumSchema);
|
|
159
161
|
return applyDefaults ? mapped.default(field.default) : mapped;
|
|
160
162
|
}
|
|
@@ -171,9 +173,9 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
171
173
|
const [first, ...rest] = field.options;
|
|
172
174
|
if (!first)
|
|
173
175
|
return z.array(z.string());
|
|
174
|
-
// `required: true`
|
|
175
|
-
//
|
|
176
|
-
// in buildInsertSchema
|
|
176
|
+
// `required: true` means non-empty, as with a `text` field.
|
|
177
|
+
// An empty array is rejected; the global `.optional()` wrapping
|
|
178
|
+
// in buildInsertSchema determines whether the field may be omitted.
|
|
177
179
|
let schema = z.array(z.enum([first, ...rest]));
|
|
178
180
|
if (field.required)
|
|
179
181
|
schema = schema.min(1);
|
|
@@ -209,10 +211,10 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
209
211
|
return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
|
|
210
212
|
}
|
|
211
213
|
case "bigInt": {
|
|
212
|
-
// JS-`number
|
|
213
|
-
// safe-integer
|
|
214
|
-
//
|
|
215
|
-
//
|
|
214
|
+
// JS-`number` round-trip via mode:"number"; exact up to 2^53.
|
|
215
|
+
// The safe-integer cap ensures that a caller passing a float (for example,
|
|
216
|
+
// due to a parseFloat bug) fails immediately at insert instead of being
|
|
217
|
+
// silently truncated.
|
|
216
218
|
const schema = z.number().int().safe();
|
|
217
219
|
return field.default !== undefined && applyDefaults ? schema.default(field.default) : schema;
|
|
218
220
|
}
|
|
@@ -267,8 +269,8 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
267
269
|
});
|
|
268
270
|
}
|
|
269
271
|
case "jsonb": {
|
|
270
|
-
// Free-form jsonb — keys
|
|
271
|
-
//
|
|
272
|
+
// Free-form jsonb — keys are tenant-/runtime-defined. Validation
|
|
273
|
+
// passes through any plain object.
|
|
272
274
|
return z.record(z.string(), z.unknown());
|
|
273
275
|
}
|
|
274
276
|
case "date": {
|
|
@@ -276,26 +278,26 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
276
278
|
return withDateBounds(schema, field.min, field.max);
|
|
277
279
|
}
|
|
278
280
|
case "timestamp": {
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
281
|
+
// When locatedBy is set: wall-clock time without an offset (ISO datetime
|
|
282
|
+
// without `Z`). Otherwise: ISO UTC datetime (with `Z`). Both are validated
|
|
283
|
+
// against the ISO-8601 schema through z.iso.datetime; precision (with or
|
|
284
|
+
// without an offset) depends on locatedBy.
|
|
283
285
|
const schema = field.locatedBy !== undefined ? z.iso.datetime({ local: true }) : z.iso.datetime();
|
|
284
286
|
return withDateBounds(schema, field.min, field.max);
|
|
285
287
|
}
|
|
286
288
|
case "tz": {
|
|
287
|
-
// IANA-
|
|
288
|
-
// (isValidIanaTimeZone).
|
|
289
|
-
//
|
|
289
|
+
// IANA time-zone name, validated against the runtime's zone list
|
|
290
|
+
// (isValidIanaTimeZone). An invalid name fails at the write boundary
|
|
291
|
+
// instead of later in ctx.tz.parse / Temporal.
|
|
290
292
|
return z.string().refine(isValidIanaTimeZone, { message: "invalid IANA time zone" });
|
|
291
293
|
}
|
|
292
294
|
case "locatedTimestamp": {
|
|
293
|
-
// Combined
|
|
294
|
-
// { at, tz } (
|
|
295
|
-
// (
|
|
296
|
-
//
|
|
295
|
+
// Combined wall-clock and time-zone object. On write, either
|
|
296
|
+
// { at, tz } (typical for a UI form, with utc calculated) or { utc, tz }
|
|
297
|
+
// (typical for server-to-server communication, with at calculated) is
|
|
298
|
+
// accepted. On read, the read wrapper provides all three fields.
|
|
297
299
|
//
|
|
298
|
-
//
|
|
300
|
+
// The schema guarantees only tz plus at least one of at or utc.
|
|
299
301
|
const at = z.iso.datetime({ local: true });
|
|
300
302
|
const tz = z.string().refine(isValidIanaTimeZone, { message: "invalid IANA time zone" });
|
|
301
303
|
const utc = z.iso.datetime();
|
|
@@ -322,11 +324,12 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
322
324
|
return field.maxCount === undefined ? ids : ids.max(field.maxCount);
|
|
323
325
|
}
|
|
324
326
|
case "reference":
|
|
325
|
-
//
|
|
326
|
-
// (
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
327
|
+
// Validates the UUID shape. Checking that the reference exists
|
|
328
|
+
// (the row exists in the referenced table and belongs to the tenant) is
|
|
329
|
+
// the handler / foreign-key constraint's responsibility, not the schema
|
|
330
|
+
// validator's; checking it here would require a database round trip during
|
|
331
|
+
// parsing.
|
|
332
|
+
// Multi-mode accepts an array of UUIDs.
|
|
330
333
|
return field.multiple === true ? z.array(z.uuid()) : z.uuid();
|
|
331
334
|
default:
|
|
332
335
|
assertUnreachable(field, "field type");
|
|
@@ -342,8 +345,7 @@ export function fieldToZod(field, currencies, opts = {}) {
|
|
|
342
345
|
// fails the check even if the raw minor-unit amounts happen to match.
|
|
343
346
|
//
|
|
344
347
|
// Known limitation: compares against rounded `derived` cells, i.e.
|
|
345
|
-
// "sum-of-rounded" not "round-of-sum"
|
|
346
|
-
// for a computed, read-only sibling total: kumiko-framework#1873.
|
|
348
|
+
// "sum-of-rounded" not "round-of-sum".
|
|
347
349
|
// kumiko-lint-ignore complexity-budget currency-equality check on sibling money payloads
|
|
348
350
|
function applyTotalsMatchRefinements(entity, schema) {
|
|
349
351
|
let result = schema;
|
|
@@ -360,7 +362,7 @@ function applyTotalsMatchRefinements(entity, schema) {
|
|
|
360
362
|
continue;
|
|
361
363
|
// One side of a totalsMatch pair without the other → reject. Update
|
|
362
364
|
// payloads only carry `changes`, so omitting the sibling would otherwise
|
|
363
|
-
// silently leave sum ≠ total
|
|
365
|
+
// silently leave sum ≠ total.
|
|
364
366
|
if (rawRows === undefined || siblingRaw === undefined) {
|
|
365
367
|
ctx.addIssue({
|
|
366
368
|
code: "custom",
|
|
@@ -478,7 +480,7 @@ export function buildUpdateSchema(entity, currencies = [...DEFAULT_CURRENCIES],
|
|
|
478
480
|
// sends only `{ title }` means "only change title"; zod defaults would
|
|
479
481
|
// silently inject default values for every omitted field and clobber
|
|
480
482
|
// existing data via the event-store-executor's `changes` payload.
|
|
481
|
-
// The field is passed through un-stripped
|
|
483
|
+
// The field is passed through un-stripped so
|
|
482
484
|
// fieldToZod still knows the default for its "" → default mapping
|
|
483
485
|
// (e.g. select) — applyDefaults: false only suppresses the schema-level
|
|
484
486
|
// `.default(...)` fallback for a genuinely omitted key.
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
// Screen-Filter
|
|
1
|
+
// Screen-Filter — operator-to-field-type compatibility.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// - text/select/multiSelect:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// - number/money/decimal/date/timestamp/locatedTimestamp: alle 7
|
|
10
|
-
// Ops — die Felder sind natürlich vergleichbar.
|
|
3
|
+
// Allowed operators depend on the field type:
|
|
4
|
+
// - text/select/multiSelect: equality operators only (eq, ne, in). lt/gt
|
|
5
|
+
// on strings are usually accidental; authors can simply omit the filter.
|
|
6
|
+
// - boolean: eq, ne (in/lt/gt are meaningless for a two-value type).
|
|
7
|
+
// - number/money/decimal/date/timestamp/locatedTimestamp: all 7 operators,
|
|
8
|
+
// because these fields are naturally comparable.
|
|
11
9
|
//
|
|
12
|
-
//
|
|
13
|
-
// ("filter
|
|
14
|
-
//
|
|
15
|
-
//
|
|
10
|
+
// The boot validator uses this map to catch authoring errors early
|
|
11
|
+
// ("filter with op:lt on a text field" → boot failure). When adding a field
|
|
12
|
+
// type, add it here and set its sortable/filterable flag in the type definition;
|
|
13
|
+
// otherwise the validator rejects the field entirely.
|
|
16
14
|
const EQUALITY_ONLY = ["eq", "ne", "in"];
|
|
17
15
|
const COMPARABLE = [
|
|
18
16
|
"eq",
|
|
@@ -39,17 +37,16 @@ export function getAllowedFilterOps(field) {
|
|
|
39
37
|
case "timestamp":
|
|
40
38
|
case "locatedTimestamp":
|
|
41
39
|
return COMPARABLE;
|
|
42
|
-
// tz/embedded/file/image/files/images:
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
40
|
+
// tz/embedded/file/image/files/images: not filterable — authors cannot
|
|
41
|
+
// mark the field as filterable: true because the boot validator already
|
|
42
|
+
// rejects `filterable: true` for these types; the flag is not present in
|
|
43
|
+
// their type definitions.
|
|
46
44
|
default:
|
|
47
45
|
return [];
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
// `"filterable" in field`-Narrow ab.
|
|
48
|
+
// If an author marked the field as filterable, catch types without that
|
|
49
|
+
// flag in their type definition with the `"filterable" in field` narrowing.
|
|
53
50
|
export function isFieldFilterable(field) {
|
|
54
51
|
return "filterable" in field && field.filterable === true;
|
|
55
52
|
}
|
|
@@ -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
|