@cosmicdrift/kumiko-framework 0.362.0 → 0.364.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/AGENTS.md +307 -0
- package/agents/examples/access-control.md +355 -0
- package/agents/examples/anonymous-access-multitenant.md +443 -0
- package/agents/examples/anonymous-access.md +300 -0
- package/agents/examples/apex-landing.md +528 -0
- package/agents/examples/apex-surface-auth.md +355 -0
- package/agents/examples/api-worker-split.md +434 -0
- package/agents/examples/auth-claims.md +351 -0
- package/agents/examples/auth-foundation-providers.md +155 -0
- package/agents/examples/auth-mfa.md +323 -0
- package/agents/examples/basic-entity.md +420 -0
- package/agents/examples/boot-check.md +122 -0
- package/agents/examples/chat-channels.md +254 -0
- package/agents/examples/compliance-profiles-demo.md +307 -0
- package/agents/examples/cross-feature-events.md +317 -0
- package/agents/examples/crypto-shredding-hr.md +314 -0
- package/agents/examples/currencies-global.md +609 -0
- package/agents/examples/currencies-per-tenant.md +453 -0
- package/agents/examples/custom-fields-basic.md +314 -0
- package/agents/examples/custom-handlers.md +397 -0
- package/agents/examples/delivery-notifications.md +432 -0
- package/agents/examples/designer-demo.md +86 -0
- package/agents/examples/e2e-generator.md +211 -0
- package/agents/examples/embedded-entity-form.md +598 -0
- package/agents/examples/embedded.md +649 -0
- package/agents/examples/encrypted-tenant-config.md +405 -0
- package/agents/examples/error-contract.md +539 -0
- package/agents/examples/event-sourcing.md +1287 -0
- package/agents/examples/feature-toggles.md +263 -0
- package/agents/examples/field-access.md +271 -0
- package/agents/examples/files-post-processing.md +433 -0
- package/agents/examples/i18n.md +166 -0
- package/agents/examples/idempotency.md +179 -0
- package/agents/examples/inbound-mail-triage.md +315 -0
- package/agents/examples/lane-routing.md +246 -0
- package/agents/examples/ledger-banking.md +214 -0
- package/agents/examples/ledger-invoicing.md +235 -0
- package/agents/examples/legal-pages.md +393 -0
- package/agents/examples/lifecycle-hooks.md +368 -0
- package/agents/examples/managed-config.md +428 -0
- package/agents/examples/managed-pages.md +353 -0
- package/agents/examples/notes-history-basic.md +345 -0
- package/agents/examples/ownership.md +396 -0
- package/agents/examples/pdf-audit-export.md +270 -0
- package/agents/examples/pipeline-basics.md +792 -0
- package/agents/examples/public-share-token.md +351 -0
- package/agents/examples/rate-limiting.md +211 -0
- package/agents/examples/realtime-sse.md +187 -0
- package/agents/examples/record-detail-layout.md +297 -0
- package/agents/examples/reference-data.md +151 -0
- package/agents/examples/relations.md +280 -0
- package/agents/examples/screens-nav.md +353 -0
- package/agents/examples/search.md +233 -0
- package/agents/examples/secrets-demo.md +417 -0
- package/agents/examples/security-baseline.md +187 -0
- package/agents/examples/seed-migration.md +99 -0
- package/agents/examples/session-revocation.md +259 -0
- package/agents/examples/state-machine.md +920 -0
- package/agents/examples/tags-basic.md +401 -0
- package/agents/examples/tenant-isolation.md +173 -0
- package/agents/examples/tenant-settings.md +275 -0
- package/agents/examples/tier-admin.md +347 -0
- package/agents/examples/timezones.md +366 -0
- package/agents/examples/user-data-rights.md +410 -0
- package/agents/examples/user-profile.md +226 -0
- package/agents/examples/webhook-step.md +597 -0
- package/agents/examples/wizard-form.md +379 -0
- package/agents/examples/workflow-engine.md +1731 -0
- package/agents/examples/writeform-section.md +300 -0
- package/agents/examples.md +74 -0
- package/agents/features.md +768 -0
- package/agents/widgets.md +181 -0
- package/dist/api/auth-middleware.d.ts +2 -0
- package/dist/api/auth-middleware.js +14 -5
- package/dist/api/auth-routes.js +5 -3
- package/dist/api/extra-route.d.ts +3 -0
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.js +1 -1
- package/dist/api/redis-sse-broker.js +10 -8
- package/dist/api/routes.js +48 -19
- package/dist/api/server.d.ts +2 -1
- package/dist/api/server.js +18 -6
- package/dist/api/session-cookies.d.ts +1 -0
- package/dist/api/session-cookies.js +20 -5
- package/dist/api/sse-route.js +16 -1
- package/dist/bun-db/index.d.ts +1 -1
- package/dist/bun-db/index.js +1 -1
- package/dist/bun-db/query.d.ts +12 -5
- package/dist/bun-db/query.js +88 -94
- package/dist/crypto/kek-source.js +5 -3
- package/dist/crypto/pii-field-encryption.d.ts +4 -1
- package/dist/crypto/pii-field-encryption.js +2 -2
- package/dist/db/apply-entity-event.js +2 -2
- package/dist/db/collect-table-metas.js +11 -7
- package/dist/db/cursor.js +2 -1
- package/dist/db/dialect.d.ts +1 -0
- package/dist/db/dialect.js +28 -13
- package/dist/db/eagerload.js +2 -2
- package/dist/db/entity-table-from-registry.js +1 -1
- package/dist/db/entity-table-meta.js +5 -13
- package/dist/db/event-store-executor-context.d.ts +5 -6
- package/dist/db/event-store-executor-context.js +29 -27
- package/dist/db/event-store-executor-read.js +40 -25
- package/dist/db/event-store-executor-write.js +18 -20
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/lock-guard.d.ts +4 -0
- package/dist/db/lock-guard.js +14 -0
- package/dist/db/migrate-generator.js +6 -4
- package/dist/db/migrate-runner.js +32 -35
- package/dist/db/parent-ref-clause.js +2 -2
- package/dist/db/queries/backfill-pii.js +11 -9
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.js +17 -17
- package/dist/db/queries/event-store-admin.js +6 -6
- package/dist/db/queries/event-store-transfer.js +2 -2
- package/dist/db/queries/event-store.js +2 -2
- package/dist/db/queries/pii-backfill-state.js +2 -2
- package/dist/db/queries/raw-sql.js +1 -1
- package/dist/db/queries/test-stack.js +1 -1
- package/dist/db/reference-data.js +3 -5
- package/dist/db/table-builder.js +6 -4
- package/dist/db/table-introspection.d.ts +16 -0
- package/dist/db/table-introspection.js +76 -0
- package/dist/db/tenant-db-runner.d.ts +4 -0
- package/dist/db/tenant-db-runner.js +17 -0
- package/dist/db/tenant-db.d.ts +5 -2
- package/dist/db/tenant-db.js +34 -14
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/derivatives/variant-key.js +2 -1
- package/dist/engine/boot-validator/access-declarations.js +2 -2
- package/dist/engine/boot-validator/api-ext.js +3 -3
- package/dist/engine/boot-validator/boot-check.js +1 -1
- package/dist/engine/boot-validator/config-deps.js +13 -13
- package/dist/engine/boot-validator/detail-screens.js +1 -1
- package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
- package/dist/engine/boot-validator/entity-handler.js +84 -35
- package/dist/engine/boot-validator/entity-list-screens.js +3 -3
- package/dist/engine/boot-validator/handler-field-references.js +19 -9
- package/dist/engine/boot-validator/i18n-keys.js +2 -2
- package/dist/engine/boot-validator/nav.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +9 -9
- package/dist/engine/boot-validator/projection-list-screens.js +5 -5
- package/dist/engine/boot-validator/query-output-columns.js +7 -7
- package/dist/engine/boot-validator/screens.d.ts +2 -1
- package/dist/engine/boot-validator/screens.js +155 -96
- package/dist/engine/boot-validator/url-prefill-fields.js +54 -35
- package/dist/engine/changeset-changes.js +3 -3
- package/dist/engine/define-handler.d.ts +2 -2
- package/dist/engine/define-handler.js +11 -7
- package/dist/engine/embedded-derived.js +10 -6
- package/dist/engine/extensions/escape-hatch-usage.js +2 -1
- package/dist/engine/factories.js +5 -6
- package/dist/engine/feature-ast/extractors/events.js +43 -33
- package/dist/engine/feature-ast/extractors/handlers.js +2 -0
- package/dist/engine/feature-ast/patcher.d.ts +3 -3
- package/dist/engine/feature-ast/patcher.js +3 -3
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +10 -8
- package/dist/engine/feature-ast/pattern-change-schema.js +4 -3
- package/dist/engine/feature-ast/patterns.d.ts +3 -3
- package/dist/engine/feature-ast/render.js +16 -5
- package/dist/engine/feature-changelog.d.ts +2 -1
- package/dist/engine/feature-changelog.js +8 -6
- package/dist/engine/feature-config-events-jobs.d.ts +17 -7
- package/dist/engine/feature-config-events-jobs.js +20 -5
- package/dist/engine/feature-entity-handlers.d.ts +2 -2
- package/dist/engine/feature-entity-handlers.js +12 -3
- package/dist/engine/feature-ui-extensions.js +2 -3
- package/dist/engine/handler-field-references.d.ts +21 -2
- package/dist/engine/handler-field-references.js +55 -17
- package/dist/engine/index.d.ts +3 -2
- package/dist/engine/index.js +1 -0
- package/dist/engine/ownership.js +7 -20
- package/dist/engine/pattern-library/mixed-schemas.js +0 -1
- package/dist/engine/registry-ingest.d.ts +2 -1
- package/dist/engine/registry-ingest.js +85 -2
- package/dist/engine/registry-validate.d.ts +1 -0
- package/dist/engine/registry-validate.js +38 -8
- package/dist/engine/registry.js +2 -1
- package/dist/engine/schema-builder.js +3 -4
- package/dist/engine/soft-delete-cleanup.js +12 -4
- package/dist/engine/steps/call-feature.js +1 -3
- package/dist/engine/steps/retry.js +2 -1
- package/dist/engine/steps/unsafe-projection-delete.js +2 -2
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -15
- package/dist/engine/template-variables.d.ts +32 -0
- package/dist/engine/template-variables.js +80 -0
- package/dist/engine/tier-resolver-extension.js +2 -3
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/engine/validate-projection-allowlist.js +12 -13
- package/dist/engine/where-rule-lint.js +8 -7
- package/dist/env/_zod-introspect.d.ts +1 -0
- package/dist/errors/classes.js +1 -0
- package/dist/errors/kumiko-error.d.ts +2 -0
- package/dist/errors/kumiko-error.js +2 -0
- package/dist/errors/reasons.d.ts +1 -0
- package/dist/errors/reasons.js +2 -0
- package/dist/errors/write-error-info.js +2 -0
- package/dist/event-store/tenant-db-append.js +2 -2
- package/dist/files/assert-file-refs-belong-to-tenant.d.ts +4 -5
- package/dist/files/assert-file-refs-belong-to-tenant.js +7 -15
- package/dist/files/bind-pending-file-refs.d.ts +2 -0
- package/dist/files/bind-pending-file-refs.js +3 -3
- package/dist/files/bind-referenced-files.write.d.ts +1 -1
- package/dist/files/bind-referenced-files.write.js +22 -13
- package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
- package/dist/files/create-file-ref-from-bytes.js +67 -4
- package/dist/files/create-file-ref.d.ts +22 -0
- package/dist/files/create-file-ref.js +53 -25
- package/dist/files/file-handle.d.ts +1 -1
- package/dist/files/file-handle.js +8 -0
- package/dist/files/file-routes.js +2 -2
- package/dist/files/index.d.ts +2 -2
- package/dist/files/index.js +1 -1
- package/dist/files/load-save-file-refs.d.ts +10 -0
- package/dist/files/load-save-file-refs.js +47 -0
- package/dist/files/local-provider.js +2 -1
- package/dist/files/purge-expired-file-ref-bytes.d.ts +4 -1
- package/dist/files/purge-expired-file-ref-bytes.js +45 -19
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/mail-registry.d.ts +2 -0
- package/dist/i18n/mail-registry.js +7 -0
- package/dist/jobs/index.d.ts +1 -1
- package/dist/jobs/job-runner.d.ts +4 -1
- package/dist/jobs/job-runner.js +13 -9
- package/dist/observability/escape-hatch-report.d.ts +1 -1
- package/dist/observability/escape-hatch-report.js +17 -5
- package/dist/pipeline/cascade-handler.js +5 -2
- package/dist/pipeline/dispatch-batch.js +79 -31
- package/dist/pipeline/dispatch-query.js +2 -3
- package/dist/pipeline/dispatch-shared.d.ts +7 -2
- package/dist/pipeline/dispatch-shared.js +142 -34
- package/dist/pipeline/dispatch-write.js +34 -22
- package/dist/pipeline/event-dispatcher-delivery.js +2 -3
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +2 -0
- package/dist/pipeline/member-read-only-transaction.js +1 -1
- package/dist/pipeline/notify-scope.d.ts +13 -0
- package/dist/pipeline/notify-scope.js +32 -0
- package/dist/pipeline/pre-transaction-reservations.d.ts +3 -1
- package/dist/pipeline/pre-transaction-reservations.js +28 -9
- package/dist/pipeline/system-hooks.js +2 -1
- package/dist/pipeline/system-identity-switch.js +2 -2
- package/dist/rate-limit/bucket.d.ts +3 -3
- package/dist/rate-limit/bucket.js +14 -4
- package/dist/redis/cache-sync-bus.js +9 -3
- package/dist/search/meilisearch-adapter.js +39 -13
- package/dist/secrets/stored-envelope.js +7 -7
- package/dist/stack/push-entity-projection-tables.js +2 -3
- package/dist/stack/table-helpers.js +10 -26
- package/dist/stack/test-stack.js +2 -5
- package/dist/testing/multipart-helper.d.ts +0 -6
- package/dist/testing/multipart-helper.js +5 -5
- package/dist/testing/without-ambient-temporal.js +4 -4
- package/dist/utils/is-plain-object.d.ts +1 -2
- package/dist/utils/is-plain-object.js +1 -4
- package/package.json +7 -5
- package/src/changes.json +251 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as z from "zod";
|
|
2
2
|
import { type ZodType } from "zod";
|
|
3
3
|
import type { FeatureBuilderState } from "./feature-builder-state.js";
|
|
4
|
-
import type { AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, DeclarativeEventMigration, EventDef, EventPiiStance, EventUpcastFn, JobDefinition, JobHandlerFn, MetricOptions, NameOrRef, NotificationDataFn, NotificationRecipientFn, NotificationTemplateFn, QualifiedEventName, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, TranslationsDef } from "./types/index.js";
|
|
4
|
+
import type { AuthClaimsFn, ClaimKeyHandle, ClaimKeyType, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigSeedDef, DeclarativeEventMigration, EventDef, EventPiiStance, EventUpcastFn, JobDefinition, JobHandlerFn, MetricOptions, NameOrRef, NotificationDataFn, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, QualifiedEventName, SecretKeyHandle, SecretNamespaceHandle, SecretNamespaceOptions, SecretOptions, TemplateVariables, TranslationsDef } from "./types/index.js";
|
|
5
5
|
export declare function buildConfigEventsJobsMethods<TName extends string>(state: FeatureBuilderState, name: TName): {
|
|
6
6
|
config: {
|
|
7
7
|
<T extends ConfigKeyType>(keyName: string, def: ConfigKeyDefinition<T>): ConfigKeyHandle<T>;
|
|
@@ -14,19 +14,29 @@ export declare function buildConfigEventsJobsMethods<TName extends string>(state
|
|
|
14
14
|
notification(notificationNameOrDefinition: string | ({
|
|
15
15
|
readonly name: string;
|
|
16
16
|
} & {
|
|
17
|
-
readonly trigger
|
|
17
|
+
readonly trigger?: {
|
|
18
18
|
readonly on: NameOrRef;
|
|
19
19
|
};
|
|
20
|
-
readonly recipient
|
|
21
|
-
readonly data
|
|
20
|
+
readonly recipient?: NotificationRecipientFn;
|
|
21
|
+
readonly data?: NotificationDataFn;
|
|
22
22
|
readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
|
|
23
|
+
readonly variables?: TemplateVariables;
|
|
24
|
+
readonly record?: NotificationRecordMapping;
|
|
25
|
+
readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
|
|
26
|
+
readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
|
|
27
|
+
readonly localizedTemplate?: () => NotificationLocalizedTemplate;
|
|
23
28
|
}), definition?: {
|
|
24
|
-
readonly trigger
|
|
29
|
+
readonly trigger?: {
|
|
25
30
|
readonly on: NameOrRef;
|
|
26
31
|
};
|
|
27
|
-
readonly recipient
|
|
28
|
-
readonly data
|
|
32
|
+
readonly recipient?: NotificationRecipientFn;
|
|
33
|
+
readonly data?: NotificationDataFn;
|
|
29
34
|
readonly templates?: Readonly<Record<string, NotificationTemplateFn>>;
|
|
35
|
+
readonly variables?: TemplateVariables;
|
|
36
|
+
readonly record?: NotificationRecordMapping;
|
|
37
|
+
readonly template?: Readonly<Record<string, NotificationTemplateBody>>;
|
|
38
|
+
readonly meta?: Readonly<Record<string, NotificationTemplateMeta>>;
|
|
39
|
+
readonly localizedTemplate?: () => NotificationLocalizedTemplate;
|
|
30
40
|
}): void;
|
|
31
41
|
translations(def: TranslationsDef): void;
|
|
32
42
|
defineEvent: <const TInner extends string, TPayload>(eventName: TInner, schema: ZodType<TPayload>, options: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { escapeHatchGrantsProblem, normalizeEventPiiSubject, } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
3
|
import { ZodObject } from "zod";
|
|
3
4
|
import { isValidIanaTimeZone } from "../time/iana.js";
|
|
4
5
|
import { resolveName } from "./handler-helpers.js";
|
|
@@ -197,10 +198,25 @@ export function buildConfigEventsJobsMethods(state, name) {
|
|
|
197
198
|
: splitNamedDefinition(notificationNameOrDefinition);
|
|
198
199
|
state.notifications[notificationName] = {
|
|
199
200
|
name: notificationName,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
...(resolvedDefinition.trigger !== undefined
|
|
202
|
+
? { trigger: { on: resolveName(resolvedDefinition.trigger.on) } }
|
|
203
|
+
: {}),
|
|
204
|
+
...(resolvedDefinition.recipient !== undefined
|
|
205
|
+
? { recipient: resolvedDefinition.recipient }
|
|
206
|
+
: {}),
|
|
207
|
+
...(resolvedDefinition.data !== undefined ? { data: resolvedDefinition.data } : {}),
|
|
203
208
|
templates: resolvedDefinition.templates,
|
|
209
|
+
...(resolvedDefinition.variables !== undefined
|
|
210
|
+
? { variables: resolvedDefinition.variables }
|
|
211
|
+
: {}),
|
|
212
|
+
...(resolvedDefinition.record !== undefined ? { record: resolvedDefinition.record } : {}),
|
|
213
|
+
...(resolvedDefinition.template !== undefined
|
|
214
|
+
? { template: resolvedDefinition.template }
|
|
215
|
+
: {}),
|
|
216
|
+
...(resolvedDefinition.meta !== undefined ? { meta: resolvedDefinition.meta } : {}),
|
|
217
|
+
...(resolvedDefinition.localizedTemplate !== undefined
|
|
218
|
+
? { localizedTemplate: resolvedDefinition.localizedTemplate }
|
|
219
|
+
: {}),
|
|
204
220
|
};
|
|
205
221
|
},
|
|
206
222
|
translations(def) {
|
|
@@ -366,10 +382,9 @@ function compileEventMigration(spec) {
|
|
|
366
382
|
renameTargets.set(to, from);
|
|
367
383
|
}
|
|
368
384
|
return (payload) => {
|
|
369
|
-
if (
|
|
385
|
+
if (!isPlainObject(payload)) {
|
|
370
386
|
throw new Error("Declarative event migration expects an object payload");
|
|
371
387
|
}
|
|
372
|
-
// @cast-boundary parse — payload is guarded as a plain object above
|
|
373
388
|
const next = { ...payload };
|
|
374
389
|
for (const [from, to] of Object.entries(spec.rename ?? {})) {
|
|
375
390
|
if (from in next) {
|
|
@@ -3,7 +3,7 @@ import type { ZodType } from "zod";
|
|
|
3
3
|
import { type QueryHandlerDefinition, type StreamHandlerDefinition, type WriteHandlerDefinition } from "./define-handler.js";
|
|
4
4
|
import type { RegisterEntityCrudOptions } from "./entity-handlers.js";
|
|
5
5
|
import type { FeatureBuilderState } from "./feature-builder-state.js";
|
|
6
|
-
import type { AccessRule, AgentHandlerHints, EntityDefinition, EntityRef, EscapeHatchDeclaration, HandlerRef, NameOrRef, PayloadRateLimitOption, QueryHandlerFn, RateLimitDeclaration, RelationDefinition, StreamHandlerFn, WriteHandlerFn } from "./types/index.js";
|
|
6
|
+
import type { AccessRule, AgentHandlerHints, EntityDefinition, EntityRef, EscapeHatchDeclaration, HandlerRef, KumikoEventTypeMap, NameOrRef, PayloadRateLimitOption, QueryHandlerFn, RateLimitDeclaration, RelationDefinition, StreamHandlerFn, WriteHandlerFn } from "./types/index.js";
|
|
7
7
|
export declare function buildEntityHandlerMethods<TName extends string>(state: FeatureBuilderState, name: TName): {
|
|
8
8
|
entity(nameOrDefinition: string | ({
|
|
9
9
|
readonly name: string;
|
|
@@ -14,7 +14,7 @@ export declare function buildEntityHandlerMethods<TName extends string>(state: F
|
|
|
14
14
|
readonly entity: NameOrRef;
|
|
15
15
|
readonly name: string;
|
|
16
16
|
} & RelationDefinition), relationName?: string, definition?: RelationDefinition): void;
|
|
17
|
-
writeHandler<TName_1 extends string, TSchema extends ZodType>(nameOrDef: string | WriteHandlerDefinition<TName_1, TSchema>, schema?: TSchema, handler?: WriteHandlerFn<z.infer<TSchema>>, options?: {
|
|
17
|
+
writeHandler<TName_1 extends string, TSchema extends ZodType, TPrepared = undefined>(nameOrDef: string | WriteHandlerDefinition<TName_1, TSchema, unknown, KumikoEventTypeMap, TPrepared>, schema?: TSchema, handler?: WriteHandlerFn<z.infer<TSchema>>, options?: {
|
|
18
18
|
access: AccessRule;
|
|
19
19
|
rateLimit?: RateLimitDeclaration;
|
|
20
20
|
additionalRateLimits?: readonly PayloadRateLimitOption[];
|
|
@@ -54,6 +54,17 @@ function agentSlots(source) {
|
|
|
54
54
|
...(source.agent && { agent: source.agent }),
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
+
function transactionPhaseSlots(def) {
|
|
58
|
+
return {
|
|
59
|
+
...(def.reserveBeforeTransaction && {
|
|
60
|
+
reserveBeforeTransaction: def.reserveBeforeTransaction,
|
|
61
|
+
}),
|
|
62
|
+
...(def.beforeTransaction && {
|
|
63
|
+
beforeTransaction: def.beforeTransaction, // @cast-boundary engine-bridge
|
|
64
|
+
}),
|
|
65
|
+
...(def.transaction && { transaction: def.transaction }),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
57
68
|
function requireInlineHandlerArgs(kind, schema, handler, options) {
|
|
58
69
|
if (!schema || !handler || !options?.access) {
|
|
59
70
|
throw new Error(`${kind} inline form requires schema + handler + options.access`);
|
|
@@ -108,9 +119,7 @@ export function buildEntityHandlerMethods(state, name) {
|
|
|
108
119
|
...(def.rateLimit && { rateLimit: def.rateLimit }),
|
|
109
120
|
...(def.additionalRateLimits && { additionalRateLimits: def.additionalRateLimits }),
|
|
110
121
|
...(def.escapeHatch && { escapeHatch: def.escapeHatch }),
|
|
111
|
-
...(def
|
|
112
|
-
reserveBeforeTransaction: def.reserveBeforeTransaction,
|
|
113
|
-
}),
|
|
122
|
+
...transactionPhaseSlots(def),
|
|
114
123
|
...(def.tenantlessAnonymous && { tenantlessAnonymous: true }),
|
|
115
124
|
// Forward the pipeline-build closure so boot-validators and
|
|
116
125
|
// Designer/AI tooling can inspect the step list. Absent on
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { escapeHatchGrantsProblem } from "@cosmicdrift/kumiko-types/handlers";
|
|
2
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
3
|
import { WEBSOCKET_MAX_CONNECTIONS_PER_USER_LIMIT, WEBSOCKET_MAX_PAYLOAD_BYTES, WEBSOCKET_ROUTE_PATH_PREFIX, } from "@cosmicdrift/kumiko-types/websocket-route";
|
|
3
4
|
import { bindHookEscapeHatchGrant } from "../pipeline/system-identity-switch.js";
|
|
4
5
|
import { LifecycleHookTypes } from "./constants.js";
|
|
@@ -209,9 +210,7 @@ export function buildUiExtensionsMethods(state, name) {
|
|
|
209
210
|
// for every extension regardless of its typed hook shape.
|
|
210
211
|
const escapeHatch = optionsBag?.["escapeHatch"];
|
|
211
212
|
if (escapeHatch !== undefined) {
|
|
212
|
-
const reason =
|
|
213
|
-
? escapeHatch.reason
|
|
214
|
-
: undefined;
|
|
213
|
+
const reason = isPlainObject(escapeHatch) ? escapeHatch["reason"] : undefined;
|
|
215
214
|
if (typeof reason !== "string" || reason.trim().length === 0) {
|
|
216
215
|
throw new Error(`[Feature ${name}] r.useExtension("${extensionName}", "${resolvedEntityName}", ...) declares an invalid { escapeHatch } — ` +
|
|
217
216
|
`must be { reason: "<non-empty string>" } explaining why this usage needs unfiltered db access.`);
|
|
@@ -1,3 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
export declare const HANDLER_FIELD_REFERENCES_META_KEY = "references";
|
|
3
|
-
export
|
|
3
|
+
export type HandlerFieldReferenceMeta = string | {
|
|
4
|
+
readonly entity: string;
|
|
5
|
+
readonly by?: string;
|
|
6
|
+
};
|
|
7
|
+
export type HandlerFieldReference = {
|
|
8
|
+
readonly entity: string;
|
|
9
|
+
readonly by: string | undefined;
|
|
10
|
+
readonly multiple: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type HandlerFieldReferenceReading = {
|
|
13
|
+
readonly kind: "none";
|
|
14
|
+
} | {
|
|
15
|
+
readonly kind: "invalid";
|
|
16
|
+
readonly raw: unknown;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "ok";
|
|
19
|
+
readonly reference: HandlerFieldReference;
|
|
20
|
+
};
|
|
21
|
+
export declare function readHandlerFieldReference(schema: z.ZodType, field: string): HandlerFieldReferenceReading;
|
|
22
|
+
export declare function handlerFieldReference(schema: z.ZodType, field: string): HandlerFieldReference | undefined;
|
|
@@ -1,31 +1,69 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
import { zodDef, zodMeta, zodShapeField } from "../env/_zod-introspect.js";
|
|
3
3
|
export const HANDLER_FIELD_REFERENCES_META_KEY = "references";
|
|
4
4
|
const MAX_WRAPPER_DEPTH = 8;
|
|
5
|
-
|
|
5
|
+
const referenceMetaSchema = z.union([
|
|
6
|
+
z.string().min(1),
|
|
7
|
+
z.object({ entity: z.string().min(1), by: z.string().min(1).optional() }),
|
|
8
|
+
]);
|
|
9
|
+
function rawReferenceMeta(field) {
|
|
6
10
|
const meta = zodMeta(field);
|
|
7
11
|
if (typeof meta !== "object" || meta === null)
|
|
8
12
|
return undefined;
|
|
9
|
-
|
|
10
|
-
return typeof value === "string" ? value : undefined;
|
|
13
|
+
return Reflect.get(meta, HANDLER_FIELD_REFERENCES_META_KEY);
|
|
11
14
|
}
|
|
12
|
-
|
|
13
|
-
// from the outer type, so every wrapper layer is probed on the way in.
|
|
14
|
-
export function handlerFieldReference(schema, field) {
|
|
15
|
+
function unwrapObjectSchema(schema) {
|
|
15
16
|
let current = schema;
|
|
16
17
|
for (let depth = 0; depth < MAX_WRAPPER_DEPTH && current !== undefined; depth++) {
|
|
17
|
-
if (current instanceof ZodObject)
|
|
18
|
-
|
|
18
|
+
if (current instanceof z.ZodObject)
|
|
19
|
+
return current;
|
|
19
20
|
current = zodDef(current)?.innerType;
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
// `.optional()` / `.nullable()` / `.default()` after `.meta()` hide the meta from the outer
|
|
25
|
+
// type, so every wrapper layer is probed on the way in; array elements are probed too.
|
|
26
|
+
function findRawReferenceMeta(field) {
|
|
27
|
+
let current = field;
|
|
28
|
+
for (let depth = 0; depth < MAX_WRAPPER_DEPTH && current !== undefined; depth++) {
|
|
29
|
+
const raw = rawReferenceMeta(current);
|
|
30
|
+
if (raw !== undefined)
|
|
31
|
+
return raw;
|
|
32
|
+
current = current instanceof z.ZodArray ? zodDef(current)?.element : zodDef(current)?.innerType;
|
|
29
33
|
}
|
|
30
34
|
return undefined;
|
|
31
35
|
}
|
|
36
|
+
function isArrayField(field) {
|
|
37
|
+
let current = field;
|
|
38
|
+
for (let depth = 0; depth < MAX_WRAPPER_DEPTH && current !== undefined; depth++) {
|
|
39
|
+
if (current instanceof z.ZodArray)
|
|
40
|
+
return true;
|
|
41
|
+
current = zodDef(current)?.innerType;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
export function readHandlerFieldReference(schema, field) {
|
|
46
|
+
const objectSchema = unwrapObjectSchema(schema);
|
|
47
|
+
const fieldSchema = objectSchema && zodShapeField(objectSchema, field);
|
|
48
|
+
if (fieldSchema === undefined)
|
|
49
|
+
return { kind: "none" };
|
|
50
|
+
const raw = findRawReferenceMeta(fieldSchema);
|
|
51
|
+
if (raw === undefined)
|
|
52
|
+
return { kind: "none" };
|
|
53
|
+
const parsed = referenceMetaSchema.safeParse(raw);
|
|
54
|
+
if (!parsed.success)
|
|
55
|
+
return { kind: "invalid", raw };
|
|
56
|
+
const meta = parsed.data;
|
|
57
|
+
return {
|
|
58
|
+
kind: "ok",
|
|
59
|
+
reference: {
|
|
60
|
+
entity: typeof meta === "string" ? meta : meta.entity,
|
|
61
|
+
by: typeof meta === "string" ? undefined : meta.by,
|
|
62
|
+
multiple: isArrayField(fieldSchema),
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function handlerFieldReference(schema, field) {
|
|
67
|
+
const reading = readHandlerFieldReference(schema, field);
|
|
68
|
+
return reading.kind === "ok" ? reading.reference : undefined;
|
|
69
|
+
}
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export { addPattern, applyChanges, createFeaturePatcher, getPatternSchema, group
|
|
|
43
43
|
export { type ChangelogEntry, type ChangelogType, compareVersions, type FeatureChangelog, filterEntriesAfter, parseFeatureChangelog, sortEntries, validateChangelog, } from "./feature-changelog.js";
|
|
44
44
|
export { type BuildManifestOptions, buildManifestFromRegistry, type FeatureManifest, type ManifestConfigKey, type ManifestExtension, type ManifestFeature, type ManifestSecret, type ManifestSystemEvent, serializeManifest, } from "./feature-manifest.js";
|
|
45
45
|
export { checkWriteFieldOwnership, checkWriteFieldRoles, filterReadFields, maskWriteOnlyFields, } from "./field-access.js";
|
|
46
|
-
export { HANDLER_FIELD_REFERENCES_META_KEY, handlerFieldReference, } from "./handler-field-references.js";
|
|
46
|
+
export { HANDLER_FIELD_REFERENCES_META_KEY, type HandlerFieldReference, type HandlerFieldReferenceMeta, handlerFieldReference, } from "./handler-field-references.js";
|
|
47
47
|
export { resolveName, withResponseData } from "./handler-helpers.js";
|
|
48
48
|
export { i18nKey } from "./i18n-key.js";
|
|
49
49
|
export { buildSessionRoles, findForbiddenMembershipRole, isForbiddenMembershipRole, stripForbiddenMembershipRoles, } from "./membership-roles.js";
|
|
@@ -77,10 +77,11 @@ export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.
|
|
|
77
77
|
export { unwrapWorkflowStepError, WorkflowStepError } from "./steps/workflow-step-error.js";
|
|
78
78
|
export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
|
|
79
79
|
export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
|
|
80
|
+
export { assertNotificationVariables, templateVariable, templateVariablesToZod, } from "./template-variables.js";
|
|
80
81
|
export { type EffectiveFeaturesResolver, findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, type TierResolverPlugin, type TrialGate, } from "./tier-resolver-extension.js";
|
|
81
82
|
export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, type TransferAdjacency, type TransferEdge, type TransferEdgeLink, } from "./transfer-adjacency.js";
|
|
82
83
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
|
83
|
-
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationRecipientFn, NotificationTemplateFn, NotifyDelivery, NotifyDeliveryStatus, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
84
|
+
export type { AccessRule, ActionFormScreenDefinition, ActiveMembership, ActiveMembershipRejection, ActiveMembershipResult, AgentExposure, AgentHandlerHints, AgentRisk, AppContext, AppendEventArgs, AppendEventFn, AuthClaimsContext, AuthClaimsFn, AuthClaimsHookDef, BeforeTransactionPhase, BeforeTransactionResult, BelongsToRelation, BigIntFieldDef, BooleanFieldDef, CamelToKebab, ChatSendFailureCode, ClaimKeyDefinition, ClaimKeyHandle, ClaimKeyJsType, ClaimKeyType, ConcurrencyMode, ConfigAccessor, ConfigAccessorFactory, ConfigBacking, ConfigCascade, ConfigCascadeLevel, ConfigDefinition, ConfigEditScreenDefinition, ConfigKeyAccess, ConfigKeyDefinition, ConfigKeyHandle, ConfigKeyType, ConfigMask, ConfigResolver, ConfigScope, ConfigSecretsReader, ConfigSeedDef, ConfigStoredRow, ConfigStoredRowWithSource, ConfigValidateContext, ConfigValidateFn, ConfigValue, ConfigValueSource, ConfigValueWithSource, ContentCollectionDefinition, CreateSeedOptions, CreateTenantSeedOptions, CreateUserSeedOptions, CustomScreenDefinition, CustomScreenRoute, DashboardChartPanel, DashboardCustomPanel, DashboardDateParam, DashboardFeedPanel, DashboardFilterDefinition, DashboardI18nText, DashboardListPanel, DashboardMoneyParam, DashboardPanelDefinition, DashboardPanelVisibility, DashboardProgressListPanel, DashboardScreenDefinition, DashboardScreenPanel, DashboardStatGroupPanel, DashboardStatPanel, DashboardText, DashboardTextParam, DashboardValueFormat, DateFieldDef, DecimalFieldDef, DeleteContext, DeliveryErrorCode, DeliveryFailureCode, DeliverySkipReason, DeriveContext, DerivedFieldDef, DerivedFieldsMap, DerivedValueType, EditExtensionSection, EditFieldPrefill, EditFieldSpec, EditFieldsSection, EditLayout, EditRelatedListSection, EditSectionSpec, EmbeddedListEditSpec, EntityDefinition, EntityEditAction, EntityEditActionPlacement, EntityEditScreenDefinition, EntityId, EntityListExpandableRow, EntityListScreenDefinition, EntityRef, EntityRelations, EscapeHatchAuditSink, EscapeHatchDeclaration, EscapeHatchKind, EscapeHatchReporter, EscapeHatchTarget, EscapeHatchUseEvent, EventDef, ExtensionOptionsArgs, ExtensionOptionsFor, FeatureDefinition, FeatureRegistrar, FieldAccess, FieldCondition, FieldDefinition, FieldFormatRegistry, FieldIconKey, FieldRenderer, FileFieldDef, FilesFieldDef, Findability, FormatSpec, FormWidth, HandlerContext, HasManyRelation, HookMap, ImageFieldDef, ImagesFieldDef, JobBackoff, JobBackoffStrategy, JobContext, JobDefinition, JobHandlerFn, JobTrigger, JobTriggerEvent, JsonbFieldDef, KumikoEntityTypeMap, KumikoEventTypeMap, KumikoExtensionOptionsMap, KumikoHandlerPayloadMap, KumikoHandlerResultMap, LifecycleHookType, ListColumnSpec, ListFacetSpec, ListPaginationMode, ListSortDir, ListSortSpec, LongTextFindability, ManyToManyRelation, MemberReader, MetricNavigate, MetricSpec, MspErrorMode, MspErrorPolicy, MultiSelectFieldDef, MultiStreamApplyFn, MultiStreamProjectionDefinition, NameOrRef, NavDefinition, NavIconKey, NotificationDataFn, NotificationDefinition, NotificationLocalizedTemplate, NotificationRecipientFn, NotificationRecordMapping, NotificationTemplateBody, NotificationTemplateFn, NotificationTemplateMeta, NotifyDeferred, NotifyDelivery, NotifyDeliveryStatus, NotifyDispatched, NotifyFactory, NotifyFn, NotifyJobDispatcher, NotifyOptions, NotifyPriority, NotifyResult, NumberFieldDef, OnDeleteStrategy, OpenToAllAccessRule, OpenToAllDeclaration, OpenToAllPersonalData, ParentRefDef, PersonalAnnotations, PersonalAnnotationsLongText, PersonalAnnotationsNoFind, PersonalSubject, PlatformComponent, PluralForms, PostDeleteHookFn, PostSaveHookFn, PreDeleteHookFn, PreparedWriteEvent, PreQueryHookFn, PreSaveHookFn, ProjectionDefinition, ProjectionDetailScreenDefinition, ProjectionListScreenDefinition, ProjectionTable, QualifiedEventName, QueryEvent, QueryHandlerDef, QueryHandlerFn, RateLimitPer, RecordHeaderSpec, RecordHeaderSubtitlePart, ReferenceDataDef, Registry, RelationDefinition, ResolvedPiiFlags, RetentionDef, RoleAccessPersonalData, RoleAccessRule, RowAction, RowActionNavigate, RowActionNavigateBase, RowActionWriteHandler, RowFieldExtractor, SaveContext, ScreenDefinition, ScreenFilter, ScreenFilterOp, ScreenNavSugar, ScreenSlots, SecretKeyDefinition, SecretKeyHandle, SecretMintConfirmStep, SecretMintScreenDefinition, SecretNamespaceDefinition, SecretNamespaceHandle, SecretNamespaceOptions, SecretReveal, SecretRevealField, SecretsEditScreenDefinition, SecretsEditSection, SelectFieldDef, SessionUser, SessionUserOrigin, StreamHandlerDef, StreamHandlerFn, Subscribe, TargetRef, TemplateListVariableDef, TemplateScalarVariableDef, TemplateVariableDef, TemplateVariables, TenantId, TenantWriteTarget, TextFieldDef, ToolbarAction, TranslationEntry, TranslationKeys, TranslationsDef, TranslationValue, TreeAction, TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe, TreeNode, TreeNodeState, UnitKey, UnsafeAppendEventFn, ValidationError, ValidationHookFn, WorkspaceDefinition, WriteEvent, WriteHandlerDef, WriteHandlerFn, WriteResult, } from "./types/index.js";
|
|
84
85
|
export { DEFAULT_CURRENCIES, DEFAULT_LOCALES, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HookPhases, HTTP_ERROR_CODE_PATTERN, isAgentVisibleScreen, isDeliveryErrorCode, isOpenToAllGranted, resolveAgentExposure, } from "./types/index.js";
|
|
85
86
|
export type { AwaitedEventType, PipelineBuildCtx, PipelineCtx, PipelineDef, StepBuilder, StepDef, StepFailureStrategy, StepInstance, StepKind, StepNamespace, StepResolver, } from "./types/step.js";
|
|
86
87
|
export { runValidation } from "./validation.js";
|
package/dist/engine/index.js
CHANGED
|
@@ -70,6 +70,7 @@ export { describeWorkflowStepError } from "./steps/describe-workflow-step-error.
|
|
|
70
70
|
export { unwrapWorkflowStepError, WorkflowStepError } from "./steps/workflow-step-error.js";
|
|
71
71
|
export { WORKFLOW_SYSTEM_EVENT_DEFS } from "./steps/workflow-system-events.js";
|
|
72
72
|
export { ANONYMOUS_ROLE, ANONYMOUS_USER_ID, createAnonymousUser, createSystemUser, SYSTEM_ROLE, SYSTEM_USER_ID, } from "./system-user.js";
|
|
73
|
+
export { assertNotificationVariables, templateVariable, templateVariablesToZod, } from "./template-variables.js";
|
|
73
74
|
export { findTierResolverUsage, isTierResolverPlugin, TENANT_TIER_RESOLVER_EXT, } from "./tier-resolver-extension.js";
|
|
74
75
|
export { findOverDeepTransferChain, resolveTransferAdjacency as resolveEntityTransferAdjacency, } from "./transfer-adjacency.js";
|
|
75
76
|
export { isSystemTenant, isUuid, parseTenantId, SYSTEM_TENANT_ID } from "./types/identifiers.js";
|
package/dist/engine/ownership.js
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
//
|
|
17
17
|
// Construction: use the `from(ref, column?)` helper. It returns a FromRule
|
|
18
18
|
// ready to drop into an access map.
|
|
19
|
-
import {
|
|
19
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
20
20
|
import { toSnakeCase } from "../db/table-builder.js";
|
|
21
|
+
import { columnSqlNameOf, schemaTableName } from "../db/table-introspection.js";
|
|
21
22
|
import { assertQualifiedWhereFragment, tableColumnSqlNames } from "./where-rule-lint.js";
|
|
22
23
|
// Parse an OwnershipRef into kind + resolved path + default column.
|
|
23
24
|
// Throws on malformed input so the error surfaces at `from()`-call-site (in
|
|
@@ -185,32 +186,18 @@ export function userCanCreateFieldRow(user, accessMap, newRow) {
|
|
|
185
186
|
const PASS_CLAUSE = { kind: "pass" };
|
|
186
187
|
const EMPTY_CLAUSE = { kind: "empty" };
|
|
187
188
|
export function tableNameOf(table) {
|
|
188
|
-
|
|
189
|
-
const sym = table[KUMIKO_NAME_SYMBOL];
|
|
190
|
-
if (typeof sym === "string")
|
|
191
|
-
return sym;
|
|
192
|
-
}
|
|
193
|
-
return "<unknown>";
|
|
189
|
+
return schemaTableName(table) ?? "<unknown>";
|
|
194
190
|
}
|
|
195
191
|
// Resolve a JS-field name on the table to its underlying SQL column name.
|
|
196
|
-
// Drizzle tables carry the mapping under Symbol.for("kumiko:schema:Columns");
|
|
197
|
-
// we read it without importing drizzle-orm at runtime.
|
|
198
192
|
function columnSqlName(table, field) {
|
|
199
193
|
if (table === null || typeof table !== "object")
|
|
200
194
|
return null;
|
|
201
|
-
const
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
if (col && typeof col === "object") {
|
|
205
|
-
const nameVal = col["name"];
|
|
206
|
-
if (typeof nameVal === "string")
|
|
207
|
-
return nameVal;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
195
|
+
const handleName = columnSqlNameOf(table, field);
|
|
196
|
+
if (handleName !== undefined)
|
|
197
|
+
return handleName;
|
|
210
198
|
// Field may already be the SQL column name on plain objects (tests, etc.).
|
|
211
|
-
if (table[field] !== undefined)
|
|
199
|
+
if (isPlainObject(table) && table[field] !== undefined)
|
|
212
200
|
return toSnakeCase(field);
|
|
213
|
-
}
|
|
214
201
|
return null;
|
|
215
202
|
}
|
|
216
203
|
function quoteIdent(name) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { RegistryState } from "./registry-state.js";
|
|
2
|
-
import type { FeatureDefinition } from "./types/index.js";
|
|
2
|
+
import type { FeatureDefinition, NotificationDefinition } from "./types/index.js";
|
|
3
3
|
export declare function populateFeatureCore(state: RegistryState, feature: FeatureDefinition): void;
|
|
4
4
|
export declare function populateHandlers(state: RegistryState, feature: FeatureDefinition): void;
|
|
5
5
|
export declare function populateConfigKeys(state: RegistryState, feature: FeatureDefinition): void;
|
|
6
|
+
export declare function validateNotificationTemplate(featureName: string, notificationName: string, notifDef: NotificationDefinition): void;
|
|
6
7
|
export declare function populateJobsAndNotifications(state: RegistryState, feature: FeatureDefinition): void;
|
|
7
8
|
export declare function populateEvents(state: RegistryState, feature: FeatureDefinition): void;
|
|
8
9
|
export declare function populateSystemEvents(state: RegistryState): void;
|
|
@@ -90,6 +90,86 @@ export function populateConfigKeys(state, feature) {
|
|
|
90
90
|
state.configKeyMap.set(qualifiedKey, keyDef);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
// Mirrors isDocumentReferenceName in bundled-features/template-resolver: the
|
|
94
|
+
// framework cannot import bundled-features, and both sides must accept the
|
|
95
|
+
// same placeholder names.
|
|
96
|
+
const TEMPLATE_VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
97
|
+
const FORBIDDEN_TEMPLATE_VARIABLE_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
98
|
+
// The first four mirror SYSTEM_PLACEHOLDERS in bundled-features/template-resolver: the
|
|
99
|
+
// renderer fills these itself, so a declared variable of the same name would
|
|
100
|
+
// be shadowed or shadow them.
|
|
101
|
+
const RESERVED_TEMPLATE_VARIABLE_NAMES = new Set([
|
|
102
|
+
"tenantName",
|
|
103
|
+
"signature",
|
|
104
|
+
"imprint",
|
|
105
|
+
"unsubscribeUrl",
|
|
106
|
+
// Handlebars helpers of the mail renderer win over context values of the
|
|
107
|
+
// same name, so a variable called like a helper would never render.
|
|
108
|
+
"link",
|
|
109
|
+
"money",
|
|
110
|
+
"date",
|
|
111
|
+
]);
|
|
112
|
+
const KEBAB_NOTIFICATION_NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
113
|
+
function isTemplateVariableName(value) {
|
|
114
|
+
return (TEMPLATE_VARIABLE_NAME.test(value) &&
|
|
115
|
+
!value.split(".").some((segment) => FORBIDDEN_TEMPLATE_VARIABLE_SEGMENTS.has(segment)));
|
|
116
|
+
}
|
|
117
|
+
function notificationLabel(featureName, notificationName) {
|
|
118
|
+
return `Notification "${featureName}:notify:${notificationName}"`;
|
|
119
|
+
}
|
|
120
|
+
function assertTriggerGroupComplete(label, notifDef) {
|
|
121
|
+
if (notifDef.trigger !== undefined) {
|
|
122
|
+
const missing = [
|
|
123
|
+
notifDef.recipient === undefined ? "recipient" : undefined,
|
|
124
|
+
notifDef.data === undefined ? "data" : undefined,
|
|
125
|
+
].filter((name) => name !== undefined);
|
|
126
|
+
if (missing.length > 0) {
|
|
127
|
+
throw new Error(`${label} declares a trigger but no ${missing.join(" and ")}: trigger, recipient and data belong together`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// localizedTemplate exists because translations are only complete once every
|
|
132
|
+
// locale feature is built; the registry resolves it into the static template
|
|
133
|
+
// + meta that all downstream consumers (seeding, validation) already read.
|
|
134
|
+
function resolveLocalizedTemplate(featureName, notificationName, notifDef) {
|
|
135
|
+
const { localizedTemplate, ...rest } = notifDef;
|
|
136
|
+
if (localizedTemplate === undefined)
|
|
137
|
+
return rest;
|
|
138
|
+
if (rest.template !== undefined || rest.meta !== undefined) {
|
|
139
|
+
throw new Error(`${notificationLabel(featureName, notificationName)} declares localizedTemplate together with template/meta: pick one`);
|
|
140
|
+
}
|
|
141
|
+
const byLocale = Object.entries(localizedTemplate());
|
|
142
|
+
return {
|
|
143
|
+
...rest,
|
|
144
|
+
template: Object.fromEntries(byLocale.map(([locale, entry]) => [locale, { body: entry.body }])),
|
|
145
|
+
meta: Object.fromEntries(byLocale.map(([locale, entry]) => [locale, entry.meta])),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function validateNotificationTemplate(featureName, notificationName, notifDef) {
|
|
149
|
+
// skip: a notification without a template has nothing to validate
|
|
150
|
+
if (notifDef.template === undefined)
|
|
151
|
+
return;
|
|
152
|
+
const where = notificationLabel(featureName, notificationName);
|
|
153
|
+
if (!KEBAB_NOTIFICATION_NAME.test(notificationName)) {
|
|
154
|
+
throw new Error(`${where} declares a template, so its name must be kebab-case (lowercase, digits, dashes): the name becomes the template slug`);
|
|
155
|
+
}
|
|
156
|
+
for (const locale of Object.keys(notifDef.template)) {
|
|
157
|
+
if (notifDef.meta?.[locale]?.subject === undefined) {
|
|
158
|
+
throw new Error(`${where} has a template for locale "${locale}" but no meta.${locale}.subject`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (const [name, def] of Object.entries(notifDef.variables ?? {})) {
|
|
162
|
+
if (RESERVED_TEMPLATE_VARIABLE_NAMES.has(name)) {
|
|
163
|
+
throw new Error(`${where} declares variable "${name}" which collides with a system placeholder (${[...RESERVED_TEMPLATE_VARIABLE_NAMES].join(", ")})`);
|
|
164
|
+
}
|
|
165
|
+
const names = def.type === "list" ? [name, ...Object.keys(def.item)] : [name];
|
|
166
|
+
for (const candidate of names) {
|
|
167
|
+
if (!isTemplateVariableName(candidate)) {
|
|
168
|
+
throw new Error(`${where} declares variable "${candidate}" which is not a valid placeholder name (letters, digits, underscore; no prototype segments)`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
93
173
|
// Jobs (runIn-pinned, duplicate-guarded) + notifications (trigger resolved later).
|
|
94
174
|
export function populateJobsAndNotifications(state, feature) {
|
|
95
175
|
// Jobs: scope:job:name
|
|
@@ -115,10 +195,13 @@ export function populateJobsAndNotifications(state, feature) {
|
|
|
115
195
|
// Notifications: scope:notify:name
|
|
116
196
|
for (const [name, notifDef] of Object.entries(feature.notifications ?? {})) {
|
|
117
197
|
const qualifiedName = qualify(feature.name, "notify", name);
|
|
198
|
+
assertTriggerGroupComplete(notificationLabel(feature.name, name), notifDef);
|
|
199
|
+
const resolvedDef = resolveLocalizedTemplate(feature.name, name, notifDef);
|
|
200
|
+
validateNotificationTemplate(feature.name, name, resolvedDef);
|
|
118
201
|
state.notificationMap.set(qualifiedName, {
|
|
119
|
-
...
|
|
202
|
+
...resolvedDef,
|
|
120
203
|
name: qualifiedName,
|
|
121
|
-
trigger: { on:
|
|
204
|
+
...(resolvedDef.trigger !== undefined ? { trigger: { on: resolvedDef.trigger.on } } : {}),
|
|
122
205
|
});
|
|
123
206
|
state.notificationFeatureMap.set(qualifiedName, feature.name);
|
|
124
207
|
}
|
|
@@ -18,6 +18,7 @@ export declare function buildEntityEventUpcasterChains(state: RegistryState): vo
|
|
|
18
18
|
export declare function buildEventUpcasterChains(state: RegistryState, features: readonly FeatureDefinition[]): void;
|
|
19
19
|
export declare function validateProjectionApplyKeys(state: RegistryState): void;
|
|
20
20
|
export declare function validateRequiredFeatures(state: RegistryState, features: readonly FeatureDefinition[]): void;
|
|
21
|
+
export declare function validateNotificationRecordMappings(state: RegistryState): void;
|
|
21
22
|
export declare function resolveNotificationTriggersAndRegisterHooks(state: RegistryState): void;
|
|
22
23
|
export declare function validateLifecycleHookTargets(state: RegistryState): void;
|
|
23
24
|
export declare function validateEntityHookTargets(state: RegistryState, features: readonly FeatureDefinition[]): void;
|
|
@@ -520,11 +520,41 @@ export function validateRequiredFeatures(state, features) {
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
|
+
// The record mapping feeds template variables from an entity row, so every part
|
|
524
|
+
// of it is checked against the finished registry: entity, variables, field roots.
|
|
525
|
+
export function validateNotificationRecordMappings(state) {
|
|
526
|
+
for (const [qualifiedName, notifDef] of state.notificationMap) {
|
|
527
|
+
const { record, variables } = notifDef;
|
|
528
|
+
if (record === undefined)
|
|
529
|
+
continue;
|
|
530
|
+
const where = `Notification "${qualifiedName}" record mapping`;
|
|
531
|
+
const entity = state.entityMap.get(record.entity);
|
|
532
|
+
if (entity === undefined) {
|
|
533
|
+
throw new Error(`${where}: entity "${record.entity}" is not registered`);
|
|
534
|
+
}
|
|
535
|
+
if (variables === undefined) {
|
|
536
|
+
throw new Error(`${where} needs declared variables to map onto`);
|
|
537
|
+
}
|
|
538
|
+
for (const [variable, path] of Object.entries(record.map)) {
|
|
539
|
+
if (!Object.hasOwn(variables, variable)) {
|
|
540
|
+
throw new Error(`${where}: "${variable}" is not a declared variable`);
|
|
541
|
+
}
|
|
542
|
+
const root = path.split(".")[0] ?? "";
|
|
543
|
+
if (root !== "id" && !Object.hasOwn(entity.fields, root)) {
|
|
544
|
+
throw new Error(`${where}: path "${path}" for variable "${variable}" does not start with a field of entity "${record.entity}"`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
523
549
|
export function resolveNotificationTriggersAndRegisterHooks(state) {
|
|
524
550
|
// Resolve notification triggers and register postSave hooks
|
|
525
551
|
// Done after all features are registered so cross-feature triggers work
|
|
526
552
|
const allHandlerNames = allHandlerQns(state);
|
|
527
553
|
for (const [qualifiedName, notifDef] of state.notificationMap) {
|
|
554
|
+
const { trigger, recipient, data: buildData } = notifDef;
|
|
555
|
+
// Trigger-less notifications are only sent explicitly via ctx.notify.
|
|
556
|
+
if (trigger === undefined || recipient === undefined || buildData === undefined)
|
|
557
|
+
continue;
|
|
528
558
|
// Both maps are populated in lockstep — same key-set by construction.
|
|
529
559
|
const featureName = state.notificationFeatureMap.get(qualifiedName); // @cast-boundary engine-bridge
|
|
530
560
|
// I'll try the easy path first: if the trigger is already a fully qualified QN
|
|
@@ -532,13 +562,13 @@ export function resolveNotificationTriggersAndRegisterHooks(state) {
|
|
|
532
562
|
// as a write handler first (the common case), then as a query. If nothing
|
|
533
563
|
// matches by then, it was a typo and I'll say so.
|
|
534
564
|
let triggerOn;
|
|
535
|
-
if (allHandlerNames.has(
|
|
536
|
-
triggerOn =
|
|
565
|
+
if (allHandlerNames.has(trigger.on)) {
|
|
566
|
+
triggerOn = trigger.on;
|
|
537
567
|
}
|
|
538
568
|
else {
|
|
539
569
|
// Try as write handler first (most common), then query
|
|
540
|
-
const writeQn = qualify(featureName, "write",
|
|
541
|
-
const queryQn = qualify(featureName, "query",
|
|
570
|
+
const writeQn = qualify(featureName, "write", trigger.on);
|
|
571
|
+
const queryQn = qualify(featureName, "query", trigger.on);
|
|
542
572
|
if (allHandlerNames.has(writeQn)) {
|
|
543
573
|
triggerOn = writeQn;
|
|
544
574
|
}
|
|
@@ -546,9 +576,9 @@ export function resolveNotificationTriggersAndRegisterHooks(state) {
|
|
|
546
576
|
triggerOn = queryQn;
|
|
547
577
|
}
|
|
548
578
|
else {
|
|
549
|
-
throw new Error(`Notification "${qualifiedName}" triggers on "${
|
|
579
|
+
throw new Error(`Notification "${qualifiedName}" triggers on "${trigger.on}" ` +
|
|
550
580
|
`but no handler with that name exists. ` +
|
|
551
|
-
`Tried: "${
|
|
581
|
+
`Tried: "${trigger.on}", "${writeQn}", and "${queryQn}"`);
|
|
552
582
|
}
|
|
553
583
|
}
|
|
554
584
|
// Update the stored definition with resolved trigger
|
|
@@ -563,12 +593,12 @@ export function resolveNotificationTriggersAndRegisterHooks(state) {
|
|
|
563
593
|
context.log?.debug(`notification ${qualifiedName}: skipping — no notify function configured on context`);
|
|
564
594
|
return;
|
|
565
595
|
}
|
|
566
|
-
const to =
|
|
596
|
+
const to = recipient(result);
|
|
567
597
|
if (to === null) {
|
|
568
598
|
context.log?.debug(`notification ${qualifiedName}: skipping — recipient resolver returned null for result ${result.id}`);
|
|
569
599
|
return;
|
|
570
600
|
}
|
|
571
|
-
const data =
|
|
601
|
+
const data = buildData(result);
|
|
572
602
|
await context.notify(qualifiedName, { to, data });
|
|
573
603
|
},
|
|
574
604
|
});
|
package/dist/engine/registry.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dedupeFeatures } from "./dedupe-features.js";
|
|
|
3
3
|
import { buildRegistryFacade } from "./registry-facade.js";
|
|
4
4
|
import { populateClaimsAndAuth, populateConfigKeys, populateEvents, populateExtensionsAndSeeds, populateFeatureCore, populateHandlers, populateHooks, populateJobsAndNotifications, populateMetricsAndSecrets, populateProjectionsAndTables, populateScreensNavWorkspaces, populateSystemEvents, populateTranslations, } from "./registry-ingest.js";
|
|
5
5
|
import { createInitialState } from "./registry-state.js";
|
|
6
|
-
import { applyExtensionUsages, autoWireSoftDeleteJobs, buildEntityEventUpcasterChains, buildEventUpcasterChains, buildImplicitProjections, buildIncomingRelations, buildSearchableSortableCaches, computeHasRateLimitedHandler, finalizeWorkspaceNavMembership, populateHandlerEntityMappings, publishEventPiiCatalog, resolveJobTriggers, resolveNotificationTriggersAndRegisterHooks, validateBootGates, validateEntityHookTargets, validateEventMigrationVersions, validateExtensionSelectors, validateExtensionUsageTargets, validateFieldAccessHandlersAreEntityMapped, validateJobBackoff, validateLifecycleHookTargets, validateLiveEntities, validateProjectionApplyKeys, validateRelationTargetsExist, validateRequiredFeatures, } from "./registry-validate.js";
|
|
6
|
+
import { applyExtensionUsages, autoWireSoftDeleteJobs, buildEntityEventUpcasterChains, buildEventUpcasterChains, buildImplicitProjections, buildIncomingRelations, buildSearchableSortableCaches, computeHasRateLimitedHandler, finalizeWorkspaceNavMembership, populateHandlerEntityMappings, publishEventPiiCatalog, resolveJobTriggers, resolveNotificationTriggersAndRegisterHooks, validateBootGates, validateEntityHookTargets, validateEventMigrationVersions, validateExtensionSelectors, validateExtensionUsageTargets, validateFieldAccessHandlersAreEntityMapped, validateJobBackoff, validateLifecycleHookTargets, validateLiveEntities, validateNotificationRecordMappings, validateProjectionApplyKeys, validateRelationTargetsExist, validateRequiredFeatures, } from "./registry-validate.js";
|
|
7
7
|
// This is where the magic happens. By "magic" I mean: precomputed maps.
|
|
8
8
|
// I build everything once at boot (hooks, relations, searchable fields, ...)
|
|
9
9
|
// so nothing has to iterate over objects at runtime. O(1) instead of O(n*m).
|
|
@@ -41,6 +41,7 @@ export function createRegistry(rawFeatures) {
|
|
|
41
41
|
validateProjectionApplyKeys(state);
|
|
42
42
|
validateRequiredFeatures(state, features);
|
|
43
43
|
resolveNotificationTriggersAndRegisterHooks(state);
|
|
44
|
+
validateNotificationRecordMappings(state);
|
|
44
45
|
validateLifecycleHookTargets(state);
|
|
45
46
|
validateEntityHookTargets(state, features);
|
|
46
47
|
resolveJobTriggers(state);
|