@cosmicdrift/kumiko-framework 0.2.3 → 0.3.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/CHANGELOG.md +52 -0
- package/package.json +124 -39
- package/src/__tests__/full-stack.integration.ts +2 -2
- package/src/api/auth-routes.ts +5 -5
- package/src/api/jwt.ts +2 -2
- package/src/api/route-registrars.ts +1 -1
- package/src/api/routes.ts +3 -3
- package/src/api/server.ts +6 -7
- package/src/compliance/profiles.ts +8 -8
- package/src/db/assert-exists-in.ts +2 -2
- package/src/db/cursor.ts +3 -3
- package/src/db/event-store-executor.ts +19 -13
- package/src/db/located-timestamp.ts +1 -1
- package/src/db/money.ts +12 -2
- package/src/db/pg-error.ts +1 -1
- package/src/db/row-helpers.ts +1 -1
- package/src/db/table-builder.ts +3 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
- package/src/engine/__tests__/build-target.test.ts +135 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +4 -4
- package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
- package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
- package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
- package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
- package/src/engine/__tests__/raw-table.test.ts +2 -2
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
- package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
- package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
- package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
- package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
- package/src/engine/__tests__/steps-read.test.ts +142 -0
- package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
- package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
- package/src/engine/__tests__/steps-workflow.test.ts +198 -0
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
- package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
- package/src/engine/boot-validator/api-ext.ts +77 -0
- package/src/engine/boot-validator/config-deps.ts +163 -0
- package/src/engine/boot-validator/entity-handler.ts +466 -0
- package/src/engine/boot-validator/index.ts +159 -0
- package/src/engine/boot-validator/ownership.ts +198 -0
- package/src/engine/boot-validator/pii-retention.ts +155 -0
- package/src/engine/boot-validator/screens-nav.ts +624 -0
- package/src/engine/boot-validator.ts +1 -1804
- package/src/engine/build-app-schema.ts +1 -1
- package/src/engine/build-target.ts +99 -0
- package/src/engine/codemod/index.ts +15 -0
- package/src/engine/codemod/pipeline-codemod.ts +641 -0
- package/src/engine/config-helpers.ts +9 -19
- package/src/engine/constants.ts +1 -1
- package/src/engine/define-feature.ts +88 -9
- package/src/engine/define-handler.ts +89 -3
- package/src/engine/define-roles.ts +2 -2
- package/src/engine/define-step.ts +28 -0
- package/src/engine/define-workflow.ts +110 -0
- package/src/engine/entity-handlers.ts +10 -9
- package/src/engine/event-helpers.ts +4 -4
- package/src/engine/factories.ts +12 -12
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
- package/src/engine/feature-ast/extractors/index.ts +74 -0
- package/src/engine/feature-ast/extractors/round1.ts +110 -0
- package/src/engine/feature-ast/extractors/round2.ts +253 -0
- package/src/engine/feature-ast/extractors/round3.ts +471 -0
- package/src/engine/feature-ast/extractors/round4.ts +1365 -0
- package/src/engine/feature-ast/extractors/round5.ts +72 -0
- package/src/engine/feature-ast/extractors/round6.ts +66 -0
- package/src/engine/feature-ast/extractors/shared.ts +177 -0
- package/src/engine/feature-ast/parse.ts +7 -0
- package/src/engine/feature-ast/patch.ts +9 -1
- package/src/engine/feature-ast/patcher.ts +10 -3
- package/src/engine/feature-ast/patterns.ts +49 -1
- package/src/engine/feature-ast/render.ts +17 -1
- package/src/engine/index.ts +45 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +6 -0
- package/src/engine/pattern-library/library.ts +42 -2
- package/src/engine/pipeline.ts +88 -0
- package/src/engine/projection-helpers.ts +1 -1
- package/src/engine/read-claim.ts +1 -1
- package/src/engine/registry.ts +30 -2
- package/src/engine/resolve-config-or-param.ts +4 -0
- package/src/engine/run-pipeline.ts +162 -0
- package/src/engine/schema-builder.ts +2 -4
- package/src/engine/state-machine.ts +1 -1
- package/src/engine/steps/_drizzle-boundary.ts +19 -0
- package/src/engine/steps/_duration-utils.ts +33 -0
- package/src/engine/steps/_no-return-guard.ts +21 -0
- package/src/engine/steps/_resolver-utils.ts +42 -0
- package/src/engine/steps/_step-dispatch-constants.ts +38 -0
- package/src/engine/steps/aggregate-append-event.ts +56 -0
- package/src/engine/steps/aggregate-create.ts +56 -0
- package/src/engine/steps/aggregate-update.ts +68 -0
- package/src/engine/steps/branch.ts +84 -0
- package/src/engine/steps/call-feature.ts +49 -0
- package/src/engine/steps/compute.ts +41 -0
- package/src/engine/steps/for-each.ts +111 -0
- package/src/engine/steps/mail-send.ts +44 -0
- package/src/engine/steps/read-find-many.ts +51 -0
- package/src/engine/steps/read-find-one.ts +58 -0
- package/src/engine/steps/retry.ts +87 -0
- package/src/engine/steps/return.ts +34 -0
- package/src/engine/steps/unsafe-projection-delete.ts +46 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
- package/src/engine/steps/wait-for-event.ts +71 -0
- package/src/engine/steps/wait.ts +69 -0
- package/src/engine/steps/webhook-send.ts +71 -0
- package/src/engine/system-user.ts +1 -1
- package/src/engine/types/feature.ts +92 -1
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/identifiers.ts +1 -0
- package/src/engine/types/index.ts +12 -1
- package/src/engine/types/step.ts +334 -0
- package/src/engine/types/target-ref.ts +21 -0
- package/src/engine/types/tree-node.ts +130 -0
- package/src/engine/types/workspace.ts +7 -0
- package/src/engine/validate-projection-allowlist.ts +161 -0
- package/src/event-store/snapshot.ts +1 -1
- package/src/event-store/upcaster-dead-letter.ts +1 -1
- package/src/event-store/upcaster.ts +1 -1
- package/src/files/file-routes.ts +1 -1
- package/src/files/types.ts +2 -2
- package/src/jobs/job-runner.ts +10 -10
- package/src/lifecycle/lifecycle.ts +0 -3
- package/src/logging/index.ts +1 -0
- package/src/logging/pino-logger.ts +11 -7
- package/src/logging/utils.ts +24 -0
- package/src/observability/prometheus-meter.ts +7 -5
- package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
- package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
- package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
- package/src/pipeline/append-event-core.ts +22 -6
- package/src/pipeline/dispatcher-utils.ts +188 -0
- package/src/pipeline/dispatcher.ts +63 -283
- package/src/pipeline/distributed-lock.ts +1 -1
- package/src/pipeline/entity-cache.ts +2 -2
- package/src/pipeline/event-consumer-state.ts +0 -13
- package/src/pipeline/event-dispatcher.ts +4 -4
- package/src/pipeline/index.ts +0 -2
- package/src/pipeline/lifecycle-pipeline.ts +6 -12
- package/src/pipeline/msp-rebuild.ts +5 -5
- package/src/pipeline/multi-stream-apply-context.ts +6 -7
- package/src/pipeline/projection-rebuild.ts +2 -2
- package/src/pipeline/projection-state.ts +0 -12
- package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
- package/src/rate-limit/resolver.ts +1 -1
- package/src/search/in-memory-adapter.ts +1 -1
- package/src/search/meilisearch-adapter.ts +3 -3
- package/src/search/types.ts +1 -1
- package/src/secrets/leak-guard.ts +2 -2
- package/src/stack/request-helper.ts +9 -5
- package/src/stack/test-stack.ts +1 -1
- package/src/testing/handler-context.ts +4 -4
- package/src/testing/http-cookies.ts +1 -1
- package/src/time/tz-context.ts +1 -2
- package/src/ui-types/index.ts +4 -0
- package/src/engine/feature-ast/extractors.ts +0 -2602
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// r.step.return — terminate the pipeline with an explicit WriteResult.
|
|
2
|
+
//
|
|
3
|
+
// Most pipelines end with a return so the handler shape stays explicit
|
|
4
|
+
// (`isSuccess: true, data: {...}`). When a pipeline omits an explicit
|
|
5
|
+
// return, run-pipeline throws — silent fallthrough would mask the most
|
|
6
|
+
// common authoring mistake (forgotten r.step.return at the end).
|
|
7
|
+
|
|
8
|
+
import { defineStep } from "../define-step";
|
|
9
|
+
import type { WriteResult } from "../types/handlers";
|
|
10
|
+
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
11
|
+
import { resolveRequired } from "./_resolver-utils";
|
|
12
|
+
|
|
13
|
+
type ReturnStepArgs = {
|
|
14
|
+
readonly resolver: StepResolver<WriteResult<unknown>>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Sentinel result-key consumed by run-pipeline to detect "this step
|
|
18
|
+
// terminates the pipeline with this WriteResult". Leading double-underscore
|
|
19
|
+
// is reserved for runtime-internal results — user code can't collide.
|
|
20
|
+
export const RETURN_RESULT_KEY = "__return";
|
|
21
|
+
|
|
22
|
+
defineStep<ReturnStepArgs, WriteResult<unknown>>({
|
|
23
|
+
kind: "return",
|
|
24
|
+
defaultFailureStrategy: "throw",
|
|
25
|
+
resultKey: () => RETURN_RESULT_KEY,
|
|
26
|
+
run: (args, ctx: PipelineCtx) => resolveRequired(args.resolver, ctx),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export function buildReturnStep<TData>(resolver: StepResolver<WriteResult<TData>>): StepInstance {
|
|
30
|
+
return {
|
|
31
|
+
kind: "return",
|
|
32
|
+
args: { resolver } satisfies ReturnStepArgs,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// r.step.unsafeProjectionDelete — delete row(s) from a read-side
|
|
2
|
+
// projection table.
|
|
3
|
+
//
|
|
4
|
+
// Sibling to unsafeProjectionUpsert with the same boot-validation
|
|
5
|
+
// contract: target table must be in the owning feature's
|
|
6
|
+
// r.requires.projection allowlist, must NOT be a registered
|
|
7
|
+
// r.entity-aggregate-table. Skips the same set of framework-protections
|
|
8
|
+
// (lifecycle hooks, field-access, audit-trail, etc.) — see
|
|
9
|
+
// step-vocabulary.md "Was unsafeProjection.* überspringt".
|
|
10
|
+
//
|
|
11
|
+
// Convention (not enforced): most legitimate read-side deletions are
|
|
12
|
+
// downstream of an aggregate event (e.g. delete-user → cascading
|
|
13
|
+
// subscription rows vanish). The right home for those is
|
|
14
|
+
// `r.multiStreamProjection.apply` keyed on the aggregate event, not
|
|
15
|
+
// an inline-step. The inline-step is appropriate when the deletion
|
|
16
|
+
// must commit in the same TX as the aggregate-mutation that triggered
|
|
17
|
+
// it (stronger consistency than an async projection). Reviewer judges.
|
|
18
|
+
|
|
19
|
+
import type { SQL, Table } from "drizzle-orm";
|
|
20
|
+
import { defineStep } from "../define-step";
|
|
21
|
+
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
22
|
+
import { asQueryTarget } from "./_drizzle-boundary";
|
|
23
|
+
import { resolveRequired } from "./_resolver-utils";
|
|
24
|
+
|
|
25
|
+
// `where` is REQUIRED — table-wide DELETE without a clause is a TRUNCATE
|
|
26
|
+
// in disguise, exactly the footgun the `unsafe`-prefix is meant to
|
|
27
|
+
// surface. If a real use-case needs full-table purge, add an explicit
|
|
28
|
+
// `r.step.unsafeProjectionTruncate` step rather than loosening this
|
|
29
|
+
// type to `SQL | undefined`.
|
|
30
|
+
type UnsafeProjectionDeleteArgs = {
|
|
31
|
+
readonly table: Table;
|
|
32
|
+
readonly where: StepResolver<SQL>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
defineStep<UnsafeProjectionDeleteArgs, void>({
|
|
36
|
+
kind: "unsafeProjectionDelete",
|
|
37
|
+
defaultFailureStrategy: "throw",
|
|
38
|
+
run: async (args, ctx: PipelineCtx) => {
|
|
39
|
+
const where = resolveRequired(args.where, ctx);
|
|
40
|
+
await ctx.db.delete(asQueryTarget(args.table)).where(where);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export function buildUnsafeProjectionDeleteStep(args: UnsafeProjectionDeleteArgs): StepInstance {
|
|
45
|
+
return { kind: "unsafeProjectionDelete", args };
|
|
46
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// r.step.unsafeProjectionUpsert — inline read-side-projection write.
|
|
2
|
+
//
|
|
3
|
+
// Idempotent on the supplied conflict-key columns (typically the
|
|
4
|
+
// natural key + tenantId). Skips lifecycle hooks, field-access,
|
|
5
|
+
// crypto-shredding, schema-versioning, audit-trail, read-access-log.
|
|
6
|
+
// See "Was unsafeProjection.* überspringt" in
|
|
7
|
+
// docs/plans/architecture/intern/step-vocabulary.md.
|
|
8
|
+
//
|
|
9
|
+
// Use only on tables explicitly declared via r.requires.projection in
|
|
10
|
+
// the owning feature. Aggregate-tables (registered via r.entity) are
|
|
11
|
+
// rejected by boot-validation — domain mutation MUST go through
|
|
12
|
+
// r.step.aggregate.*.
|
|
13
|
+
|
|
14
|
+
import { getTableColumns, type Table } from "drizzle-orm";
|
|
15
|
+
import type { PgColumn } from "drizzle-orm/pg-core";
|
|
16
|
+
import { defineStep } from "../define-step";
|
|
17
|
+
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
18
|
+
import { asQueryTarget } from "./_drizzle-boundary";
|
|
19
|
+
import { resolveRequired } from "./_resolver-utils";
|
|
20
|
+
|
|
21
|
+
type UnsafeProjectionUpsertArgs = {
|
|
22
|
+
readonly table: Table;
|
|
23
|
+
readonly on: readonly string[];
|
|
24
|
+
readonly row: StepResolver<Record<string, unknown>>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
defineStep<UnsafeProjectionUpsertArgs, void>({
|
|
28
|
+
kind: "unsafeProjectionUpsert",
|
|
29
|
+
defaultFailureStrategy: "throw",
|
|
30
|
+
run: async (args, ctx: PipelineCtx) => {
|
|
31
|
+
const resolvedRow = resolveRequired(args.row, ctx);
|
|
32
|
+
|
|
33
|
+
const columns = getTableColumns(args.table) as Record<string, unknown>;
|
|
34
|
+
const conflictTargets = args.on.map((key) => {
|
|
35
|
+
const col = columns[key];
|
|
36
|
+
if (!col) {
|
|
37
|
+
throw new Error(`unsafeProjectionUpsert: column "${key}" not found on target table`);
|
|
38
|
+
}
|
|
39
|
+
return col;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// SET clause is the same row minus the conflict-key columns —
|
|
43
|
+
// updating a key to itself is harmless but verbose.
|
|
44
|
+
const updateSet: Record<string, unknown> = {};
|
|
45
|
+
for (const [k, v] of Object.entries(resolvedRow)) {
|
|
46
|
+
if (!args.on.includes(k)) updateSet[k] = v;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// @cast-boundary drizzle-bridge — The values + set + target casts
|
|
50
|
+
// cross the drizzle type-boundary for the same reason as
|
|
51
|
+
// asQueryTarget: resolvedRow is Record<string, unknown> by design
|
|
52
|
+
// (M.1 phantom-typing limit), drizzle's typed-builder expects
|
|
53
|
+
// table-specific shapes. Step-author owns shape correctness.
|
|
54
|
+
// `as never` (not `as any`) — never is contravariantly assignable to
|
|
55
|
+
// every drizzle Insert-shape; explicit "this bypass cannot be made
|
|
56
|
+
// type-safe without lifting <TTable extends Table>" marker.
|
|
57
|
+
await ctx.db
|
|
58
|
+
.insert(asQueryTarget(args.table))
|
|
59
|
+
.values(resolvedRow as never)
|
|
60
|
+
.onConflictDoUpdate({
|
|
61
|
+
target: conflictTargets as unknown as PgColumn[],
|
|
62
|
+
set: updateSet as never,
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export function buildUnsafeProjectionUpsertStep(args: UnsafeProjectionUpsertArgs): StepInstance {
|
|
68
|
+
return { kind: "unsafeProjectionUpsert", args };
|
|
69
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// r.step.waitForEvent — suspend the workflow until a matching domain event
|
|
2
|
+
// is observed, or a timeout expires.
|
|
3
|
+
// Tier-3 / Workflow-only: only available inside defineWorkflow.
|
|
4
|
+
//
|
|
5
|
+
// Writes a kumiko:system:workflow.step.waiting-for-event event onto the
|
|
6
|
+
// workflow-run stream and returns the SUSPEND_SENTINEL. The Resume-Loop
|
|
7
|
+
// monitors for matching events (via subscription or poll); when matched,
|
|
8
|
+
// it writes workflow.step.resumed with the matched event's data.
|
|
9
|
+
//
|
|
10
|
+
// The `match` resolver is optional — when omitted, any event of the given
|
|
11
|
+
// type resumes the workflow. When provided, it receives the event payload
|
|
12
|
+
// and must return true for the event to trigger resume.
|
|
13
|
+
|
|
14
|
+
import { defineStep } from "../define-step";
|
|
15
|
+
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
16
|
+
import { addDuration } from "./_duration-utils";
|
|
17
|
+
import { resolveRequired } from "./_resolver-utils";
|
|
18
|
+
import {
|
|
19
|
+
SUSPEND_SENTINEL,
|
|
20
|
+
WORKFLOW_AGGREGATE_TYPE,
|
|
21
|
+
WORKFLOW_WAITING_FOR_EVENT_TYPE,
|
|
22
|
+
} from "./_step-dispatch-constants";
|
|
23
|
+
|
|
24
|
+
type WaitForEventArgs = {
|
|
25
|
+
readonly event: string;
|
|
26
|
+
readonly match?: StepResolver<(payload: unknown) => boolean>;
|
|
27
|
+
readonly timeout: StepResolver<string>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
defineStep<WaitForEventArgs, undefined | typeof SUSPEND_SENTINEL>({
|
|
31
|
+
kind: "workflow.waitForEvent",
|
|
32
|
+
tier: 3,
|
|
33
|
+
defaultFailureStrategy: "throw",
|
|
34
|
+
run: async (args, ctx: PipelineCtx): Promise<undefined | typeof SUSPEND_SENTINEL> => {
|
|
35
|
+
if (!ctx.workflow) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"r.step.waitForEvent is only allowed inside defineWorkflow — " +
|
|
38
|
+
"sync handlers cannot suspend.",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const timeout = resolveRequired(args.timeout, ctx);
|
|
43
|
+
|
|
44
|
+
const now = Temporal.Now.instant().toString();
|
|
45
|
+
const timeoutAt =
|
|
46
|
+
timeout.startsWith("P") || timeout.startsWith("PT") ? addDuration(now, timeout) : timeout;
|
|
47
|
+
|
|
48
|
+
await ctx.unsafeAppendEvent({
|
|
49
|
+
aggregateId: ctx.workflow.runId,
|
|
50
|
+
aggregateType: WORKFLOW_AGGREGATE_TYPE,
|
|
51
|
+
type: WORKFLOW_WAITING_FOR_EVENT_TYPE,
|
|
52
|
+
payload: {
|
|
53
|
+
eventType: args.event,
|
|
54
|
+
timeoutAt,
|
|
55
|
+
stepIndex: ctx.workflow.stepIndex,
|
|
56
|
+
workflowName: ctx.workflow.workflowName,
|
|
57
|
+
triggerEventType: ctx.event.type,
|
|
58
|
+
triggerPayload: ctx.event.payload,
|
|
59
|
+
...(ctx.workflow.definitionFingerprint && {
|
|
60
|
+
definitionFingerprint: ctx.workflow.definitionFingerprint,
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return SUSPEND_SENTINEL;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export function buildWaitForEventStep(args: WaitForEventArgs): StepInstance {
|
|
70
|
+
return { kind: "workflow.waitForEvent", args };
|
|
71
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// r.step.wait — suspend the workflow run for a given duration.
|
|
2
|
+
// Tier-3 / Workflow-only: only available inside defineWorkflow.
|
|
3
|
+
//
|
|
4
|
+
// Writes a kumiko:system:workflow.step.waiting event onto the workflow-run
|
|
5
|
+
// stream and returns the SUSPEND_SENTINEL to halt the pipeline. The
|
|
6
|
+
// Resume-Loop picks up waiting runs when the duration expires, writes
|
|
7
|
+
// workflow.step.resumed, and re-executes from the next step.
|
|
8
|
+
//
|
|
9
|
+
// The `for` resolver accepts ISO-8601 duration strings ("PT1H", "P1D")
|
|
10
|
+
// or absolute ISO timestamps ("2026-05-16T12:00:00Z").
|
|
11
|
+
|
|
12
|
+
import { defineStep } from "../define-step";
|
|
13
|
+
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
14
|
+
import { addDuration } from "./_duration-utils";
|
|
15
|
+
import { resolveRequired } from "./_resolver-utils";
|
|
16
|
+
import {
|
|
17
|
+
SUSPEND_SENTINEL,
|
|
18
|
+
WORKFLOW_AGGREGATE_TYPE,
|
|
19
|
+
WORKFLOW_WAITING_TYPE,
|
|
20
|
+
} from "./_step-dispatch-constants";
|
|
21
|
+
|
|
22
|
+
type WaitStepArgs = {
|
|
23
|
+
readonly for: StepResolver<string>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
defineStep<WaitStepArgs, undefined | typeof SUSPEND_SENTINEL>({
|
|
27
|
+
kind: "workflow.wait",
|
|
28
|
+
tier: 3,
|
|
29
|
+
defaultFailureStrategy: "throw",
|
|
30
|
+
run: async (args, ctx: PipelineCtx): Promise<undefined | typeof SUSPEND_SENTINEL> => {
|
|
31
|
+
if (!ctx.workflow) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"r.step.wait is only allowed inside defineWorkflow — " +
|
|
34
|
+
"sync handlers cannot suspend (use r.step.webhook.send with mode: 'deferred' instead).",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const duration = resolveRequired(args.for, ctx);
|
|
39
|
+
|
|
40
|
+
const now = Temporal.Now.instant().toString();
|
|
41
|
+
const wakeAt =
|
|
42
|
+
duration.startsWith("P") || duration.startsWith("PT") ? addDuration(now, duration) : duration;
|
|
43
|
+
|
|
44
|
+
await ctx.unsafeAppendEvent({
|
|
45
|
+
aggregateId: ctx.workflow.runId,
|
|
46
|
+
aggregateType: WORKFLOW_AGGREGATE_TYPE,
|
|
47
|
+
type: WORKFLOW_WAITING_TYPE,
|
|
48
|
+
payload: {
|
|
49
|
+
wakeAt,
|
|
50
|
+
stepIndex: ctx.workflow.stepIndex,
|
|
51
|
+
workflowName: ctx.workflow.workflowName,
|
|
52
|
+
// Trigger snapshot: pinned so the resume-loop re-feeds the
|
|
53
|
+
// pipeline with what the original run saw. event-sourcing across
|
|
54
|
+
// suspensions hinges on this being stable.
|
|
55
|
+
triggerEventType: ctx.event.type,
|
|
56
|
+
triggerPayload: ctx.event.payload,
|
|
57
|
+
...(ctx.workflow.definitionFingerprint && {
|
|
58
|
+
definitionFingerprint: ctx.workflow.definitionFingerprint,
|
|
59
|
+
}),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return SUSPEND_SENTINEL;
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export function buildWaitStep(args: WaitStepArgs): StepInstance {
|
|
68
|
+
return { kind: "workflow.wait", args };
|
|
69
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// r.step.webhook.send — deferred HTTP-POST via the step-dispatcher.
|
|
2
|
+
// Tier-2: requires `r.requires.step("webhook.send")` in the owning feature.
|
|
3
|
+
//
|
|
4
|
+
// Writes a `kumiko:step:dispatch-requested` event onto a fresh step-dispatch
|
|
5
|
+
// stream in the current TX. The step-dispatcher subscription (bundled-feature
|
|
6
|
+
// `step-dispatcher`) reads after COMMIT and performs the fetch with retry.
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { defineStep } from "../define-step";
|
|
10
|
+
import type { PipelineCtx, StepInstance, StepResolver } from "../types/step";
|
|
11
|
+
import { resolveOptional, resolveRequired } from "./_resolver-utils";
|
|
12
|
+
import {
|
|
13
|
+
STEP_DISPATCH_AGGREGATE_TYPE,
|
|
14
|
+
STEP_DISPATCH_REQUESTED_TYPE,
|
|
15
|
+
} from "./_step-dispatch-constants";
|
|
16
|
+
|
|
17
|
+
// Re-export for back-compat callers (bundled step-dispatcher imports
|
|
18
|
+
// these). The canonical home is _step-dispatch-constants.ts.
|
|
19
|
+
export {
|
|
20
|
+
STEP_DISPATCH_AGGREGATE_TYPE,
|
|
21
|
+
STEP_DISPATCH_FAILED_TYPE,
|
|
22
|
+
STEP_DISPATCH_REQUESTED_TYPE,
|
|
23
|
+
STEP_DISPATCHED_TYPE,
|
|
24
|
+
} from "./_step-dispatch-constants";
|
|
25
|
+
|
|
26
|
+
type WebhookHttpMethod = "POST" | "PUT" | "PATCH";
|
|
27
|
+
|
|
28
|
+
type WebhookAuth =
|
|
29
|
+
| { readonly kind: "bearer"; readonly secretRef: string }
|
|
30
|
+
| { readonly kind: "header"; readonly name: string; readonly secretRef: string };
|
|
31
|
+
|
|
32
|
+
type WebhookSendArgs = {
|
|
33
|
+
readonly url: StepResolver<string>;
|
|
34
|
+
readonly method?: WebhookHttpMethod;
|
|
35
|
+
readonly headers?: StepResolver<Readonly<Record<string, string>>>;
|
|
36
|
+
readonly body?: StepResolver<unknown>;
|
|
37
|
+
readonly auth?: WebhookAuth;
|
|
38
|
+
readonly mode: "deferred";
|
|
39
|
+
readonly retry?: { readonly times: number; readonly backoff: "exponential" | "linear" };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
defineStep<WebhookSendArgs, void>({
|
|
43
|
+
kind: "webhook.send",
|
|
44
|
+
tier: 2,
|
|
45
|
+
defaultFailureStrategy: "throw",
|
|
46
|
+
run: async (args, ctx: PipelineCtx) => {
|
|
47
|
+
const url = resolveRequired(args.url, ctx);
|
|
48
|
+
const headers = resolveOptional(args.headers, ctx) ?? {};
|
|
49
|
+
const body = resolveOptional(args.body, ctx);
|
|
50
|
+
await ctx.unsafeAppendEvent({
|
|
51
|
+
aggregateId: randomUUID(),
|
|
52
|
+
aggregateType: STEP_DISPATCH_AGGREGATE_TYPE,
|
|
53
|
+
type: STEP_DISPATCH_REQUESTED_TYPE,
|
|
54
|
+
payload: {
|
|
55
|
+
stepKind: "webhook.send",
|
|
56
|
+
spec: {
|
|
57
|
+
url,
|
|
58
|
+
method: args.method ?? "POST",
|
|
59
|
+
headers,
|
|
60
|
+
body,
|
|
61
|
+
...(args.auth && { auth: args.auth }),
|
|
62
|
+
},
|
|
63
|
+
retry: args.retry ?? { times: 3, backoff: "exponential" },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export function buildWebhookSendStep(args: WebhookSendArgs): StepInstance {
|
|
70
|
+
return { kind: "webhook.send", args };
|
|
71
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
1
|
import type { SessionUser } from "./types";
|
|
2
|
+
import type { TenantId } from "./types/identifiers";
|
|
3
3
|
|
|
4
4
|
// Stringified so it round-trips through SessionUser.id (string UUID-shape).
|
|
5
5
|
// Not a real UUID — SYSTEM acts as an alias for "no human caller" and event-
|
|
@@ -54,6 +54,7 @@ import type { NavDefinition } from "./nav";
|
|
|
54
54
|
import type { MultiStreamProjectionDefinition, ProjectionDefinition } from "./projection";
|
|
55
55
|
import type { EntityRelations, RelationDefinition } from "./relations";
|
|
56
56
|
import type { ScreenDefinition } from "./screen";
|
|
57
|
+
import type { TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe } from "./tree-node";
|
|
57
58
|
import type { WorkspaceDefinition } from "./workspace";
|
|
58
59
|
|
|
59
60
|
// --- Metrics (declared by features via r.metric()) ---
|
|
@@ -148,6 +149,14 @@ export type FeatureDefinition = {
|
|
|
148
149
|
readonly exports?: unknown;
|
|
149
150
|
readonly requires: readonly string[];
|
|
150
151
|
readonly optionalRequires: readonly string[];
|
|
152
|
+
// Read-side projection-tables this feature is allowed to write via
|
|
153
|
+
// r.step.unsafeProjectionUpsert / unsafeProjectionDelete. Declared via
|
|
154
|
+
// r.requires.projection("table_name"). Hard requirement — boot-error
|
|
155
|
+
// if a step targets a non-listed table or one that's already an
|
|
156
|
+
// r.entity-registered aggregate-table. See step-vocabulary.md Q10.
|
|
157
|
+
readonly requiredProjections: ReadonlySet<string>;
|
|
158
|
+
// Tier-2 step kinds opted-in via r.requires.step("webhook.send"). Q9.
|
|
159
|
+
readonly requiredSteps: ReadonlySet<string>;
|
|
151
160
|
// Declared via r.toggleable({ default }). Presence makes the feature
|
|
152
161
|
// operator-switchable via the feature-toggles bundled feature; absence
|
|
153
162
|
// means the feature is always-on (e.g. auth, tenant, user — core infra
|
|
@@ -224,6 +233,21 @@ export type FeatureDefinition = {
|
|
|
224
233
|
// shellWorkspaces consumes the resolved per-workspace nav list at mount
|
|
225
234
|
// time; engine validates roles + nav refs at boot.
|
|
226
235
|
readonly workspaces: Readonly<Record<string, WorkspaceDefinition>>;
|
|
236
|
+
// Tree-Actions-Map declared via r.treeActions(). At-most-one per feature
|
|
237
|
+
// (only-once-guard at registration). Erased to `Record<string,
|
|
238
|
+
// TreeActionDef>` for runtime registry-lookup (Visual-Tree-Component
|
|
239
|
+
// dispatching, Pattern-AST consumers). The compile-time-typed surface
|
|
240
|
+
// is the registrar's return value (TreeActionsHandle) which the
|
|
241
|
+
// feature exports via setup-return — buildTarget consumes the handle,
|
|
242
|
+
// not this slot. See visual-tree.md A5 + A7.
|
|
243
|
+
readonly treeActions?: Readonly<Record<string, TreeActionDef>>;
|
|
244
|
+
// Tree-Provider declared via r.tree(). At-most-one per feature.
|
|
245
|
+
// Provider liefert die Top-Level-Knoten dieses Features im Visual-
|
|
246
|
+
// Workspace (navigation: "tree"). Subscribe-Form mit lazy-Eval: erst
|
|
247
|
+
// beim Mount des Workspaces aufgerufen, kann Updates emittieren.
|
|
248
|
+
// Feature ohne treeProvider ist im Visual-Workspace unsichtbar
|
|
249
|
+
// (Zero-Whitelist-Filter aus visual-tree.md A2).
|
|
250
|
+
readonly treeProvider?: TreeChildrenSubscribe;
|
|
227
251
|
// HTTP-Routes declared via r.httpRoute(). Index is "METHOD path"
|
|
228
252
|
// (z.B. "GET /feed.xml") — eindeutig pro Feature. Die App-Server-
|
|
229
253
|
// Boot-Stage iteriert getAllHttpRoutes() und mountet jede Route auf
|
|
@@ -250,9 +274,22 @@ type RefOrRefs = NameOrRef | readonly NameOrRef[];
|
|
|
250
274
|
* keeping strict-mode alive even when handlers route via `eventDef.name`
|
|
251
275
|
* instead of hand-typed string literals.
|
|
252
276
|
*/
|
|
277
|
+
/**
|
|
278
|
+
* `r.requires` is a callable+namespace: existing call form takes feature
|
|
279
|
+
* names (`r.requires("auth", "tenant")`), the `.projection` extension
|
|
280
|
+
* declares read-side projection tables that this feature's pipeline
|
|
281
|
+
* steps are allowed to write via `r.step.unsafeProjectionUpsert`.
|
|
282
|
+
* Hard-required for any unsafeProjection-* step usage (see Q10).
|
|
283
|
+
*/
|
|
284
|
+
export type RequiresApi = ((...featureNames: string[]) => void) & {
|
|
285
|
+
readonly projection: (tableName: string) => void;
|
|
286
|
+
// Tier-2 step opt-in (Q9). Tier-1 implicit, Tier-2 must be declared.
|
|
287
|
+
readonly step: (stepKind: string) => void;
|
|
288
|
+
};
|
|
289
|
+
|
|
253
290
|
export type FeatureRegistrar<TFeature extends string = string> = {
|
|
254
291
|
systemScope(): void;
|
|
255
|
-
requires
|
|
292
|
+
requires: RequiresApi;
|
|
256
293
|
optionalRequires(...featureNames: string[]): void;
|
|
257
294
|
// Declare the feature as operator-togglable. `default` is the effective
|
|
258
295
|
// state when no global-toggle row exists. Must be called at most once per
|
|
@@ -506,6 +543,44 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
506
543
|
// a non-empty string is the contract. If you can't write a reason,
|
|
507
544
|
// declare data via `r.entity()` instead.
|
|
508
545
|
rawTable(name: string, table: PgTable, options: RawTableOptions): void;
|
|
546
|
+
|
|
547
|
+
// Register the tree-actions schema for this feature — a map of
|
|
548
|
+
// action-name → action-definition (with optional typed args). At-most-
|
|
549
|
+
// one call per feature.
|
|
550
|
+
//
|
|
551
|
+
// Returns a TreeActionsHandle that the feature exports via setup-return
|
|
552
|
+
// (Memory `[EventDef-Exports-Pattern]`). The handle carries the
|
|
553
|
+
// literal-typed action-map that `buildTarget` consumes for compile-
|
|
554
|
+
// time validation:
|
|
555
|
+
//
|
|
556
|
+
// const handle = r.treeActions({
|
|
557
|
+
// edit: { args: { slug: "" as string } },
|
|
558
|
+
// list: {},
|
|
559
|
+
// });
|
|
560
|
+
// return { handle };
|
|
561
|
+
//
|
|
562
|
+
// Without this typed return, the action-map collapses to
|
|
563
|
+
// `Record<string, TreeActionDef>` at the buildTarget call-site and
|
|
564
|
+
// every action becomes accept-anything-string. See visual-tree.md A5.
|
|
565
|
+
//
|
|
566
|
+
// The runtime FeatureDefinition.treeActions slot stores the same map
|
|
567
|
+
// as erased Record (registry lookup, Pattern-AST consumers).
|
|
568
|
+
treeActions<const TActions extends Record<string, TreeActionDef>>(
|
|
569
|
+
actions: TActions,
|
|
570
|
+
): TreeActionsHandle<TFeature, TActions>;
|
|
571
|
+
|
|
572
|
+
// Register the tree-provider for this feature — the Subscribe-Function
|
|
573
|
+
// that emits the top-level Tree-Knoten when the Visual-Workspace
|
|
574
|
+
// (navigation: "tree") mounts. At-most-one call per feature.
|
|
575
|
+
//
|
|
576
|
+
// Provider receives a TreeContext (Phase-0-stub, opaque) and an
|
|
577
|
+
// emit-function; returns an unsubscribe-function. Initial-emit synchron
|
|
578
|
+
// oder async, weitere Emits beliebig oft (e.g. on entity-update SSE).
|
|
579
|
+
//
|
|
580
|
+
// A feature without r.tree() is invisible in `navigation: "tree"`-
|
|
581
|
+
// workspaces — that's the Zero-Whitelist-Filter from visual-tree.md A2:
|
|
582
|
+
// provider-Vorhandensein ist der Filter, kein Workspace-Mapping.
|
|
583
|
+
tree(provider: TreeChildrenSubscribe): void;
|
|
509
584
|
};
|
|
510
585
|
|
|
511
586
|
// --- Registry (created from features) ---
|
|
@@ -677,4 +752,20 @@ export type Registry = {
|
|
|
677
752
|
// validator rejects more than one. Apps without a default fall back to
|
|
678
753
|
// the first workspace the user has access to.
|
|
679
754
|
getDefaultWorkspace(): WorkspaceDefinition | undefined;
|
|
755
|
+
|
|
756
|
+
// Tree-Providers declared via r.tree() across all features. Keyed by
|
|
757
|
+
// declaring feature name (NOT qualified — Provider sind feature-bound,
|
|
758
|
+
// ein Feature liefert genau eine Provider-Function). The Visual-Tree
|
|
759
|
+
// component (renderer-web) iteriert getTreeProviders() beim Mount des
|
|
760
|
+
// navigation: "tree"-Workspaces, ruft jeden Provider mit ctx auf,
|
|
761
|
+
// sammelt die emitted TreeNode[] und merged sie zur Top-Level-Liste.
|
|
762
|
+
// See visual-tree.md A2 (Zero-Whitelist) + A4 (Subscribe-Form).
|
|
763
|
+
getTreeProviders(): ReadonlyMap<string, TreeChildrenSubscribe>;
|
|
764
|
+
|
|
765
|
+
// Tree-Actions-Map des Features. Returns the erased Record (compile-
|
|
766
|
+
// time-typed handle wandert über setup-export, nicht hier). Visual-
|
|
767
|
+
// Tree-Component nutzt das für Runtime-Action-Lookup beim Klick auf
|
|
768
|
+
// einen TreeNode.target — der Resolver findet das Feature via
|
|
769
|
+
// TargetRef.featureId und holt sich die zugehörige Action-Definition.
|
|
770
|
+
getTreeActions(featureName: string): Readonly<Record<string, TreeActionDef>> | undefined;
|
|
680
771
|
};
|
|
@@ -175,9 +175,9 @@ export function withResponseData<T>(result: WriteResult<unknown>, data: T): Writ
|
|
|
175
175
|
|
|
176
176
|
// --- Context Types ---
|
|
177
177
|
|
|
178
|
-
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
179
178
|
// Forward import: Registry is in feature.ts (circular type import — fine in TS)
|
|
180
179
|
import type { Registry } from "./feature";
|
|
180
|
+
import type { TenantId } from "./identifiers";
|
|
181
181
|
|
|
182
182
|
// Minimal interface for job event triggers (framework-owned, concrete type in jobs/)
|
|
183
183
|
export type JobRunnerRef = {
|
|
@@ -312,7 +312,7 @@ export type AppContext = SharedContextFields & {
|
|
|
312
312
|
// TMap propagates the strict event-type-map through `appendEvent`. Defaults
|
|
313
313
|
// to the global KumikoEventTypeMap (augmented per app via
|
|
314
314
|
// `declare module "@cosmicdrift/kumiko-framework/engine"`). Code that bypasses the
|
|
315
|
-
// type-map (runtime-pluggable events) uses `
|
|
315
|
+
// type-map (runtime-pluggable events) uses `unsafeAppendEvent`.
|
|
316
316
|
export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedContextFields & {
|
|
317
317
|
readonly db: TenantDb;
|
|
318
318
|
readonly registry: Registry;
|
|
@@ -353,11 +353,11 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
|
|
|
353
353
|
readonly appendEvent: AppendEventFn<TMap>;
|
|
354
354
|
|
|
355
355
|
// Escape-hatch for runtime-pluggable features without a compile-time
|
|
356
|
-
// augmentation. See
|
|
356
|
+
// augmentation. See UnsafeAppendEventFn — same runtime as appendEvent,
|
|
357
357
|
// but the type-surface is `payload: unknown`. Use only when the event-
|
|
358
358
|
// type is not knowable at compile-time; otherwise the strict path
|
|
359
359
|
// (appendEvent) is the contract Designer/AI rely on.
|
|
360
|
-
readonly
|
|
360
|
+
readonly unsafeAppendEvent: UnsafeAppendEventFn;
|
|
361
361
|
|
|
362
362
|
// Marten FetchForWriting equivalent: load the current stream, optionally
|
|
363
363
|
// enforce expectedVersion, and get a handle that appends further events
|
|
@@ -428,11 +428,11 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
|
|
|
428
428
|
// name without the feature having to import the drizzle-table directly.
|
|
429
429
|
//
|
|
430
430
|
// Auto-applies tenant_id filter when the projection table has a tenant_id
|
|
431
|
-
// column (or opt out with {
|
|
431
|
+
// column (or opt out with { unsafeAllTenants: true } for system-scoped reads
|
|
432
432
|
// like cross-tenant analytics). Unknown projection name throws.
|
|
433
433
|
readonly queryProjection: <T = Record<string, unknown>>(
|
|
434
434
|
qualifiedName: string,
|
|
435
|
-
options?: { readonly
|
|
435
|
+
options?: { readonly unsafeAllTenants?: boolean },
|
|
436
436
|
) => Promise<readonly T[]>;
|
|
437
437
|
|
|
438
438
|
// Always populated — Noop when no observability provider is configured.
|
|
@@ -601,7 +601,7 @@ export type TypedAppendEventArgs<TMap extends object, K extends keyof TMap> = {
|
|
|
601
601
|
// Strict-only form. Single overload — `<K extends keyof TMap>` against the
|
|
602
602
|
// app's pre-bound TMap. No fallback overload: apps that need runtime-pluggable
|
|
603
603
|
// events (where the type-string isn't known at compile-time) reach for
|
|
604
|
-
// `
|
|
604
|
+
// `unsafeAppendEvent`.
|
|
605
605
|
//
|
|
606
606
|
// Why no fallback overload:
|
|
607
607
|
// A two-overload form (`(args: AppendEventArgs)` as the second sig)
|
|
@@ -619,13 +619,13 @@ export type TypedAppendEventArgs<TMap extends object, K extends keyof TMap> = {
|
|
|
619
619
|
// KumikoEventTypeMap>(...)` wrappers. Handlers inside those wrappers
|
|
620
620
|
// get a strict ctx.appendEvent.
|
|
621
621
|
// - Cross-package callers (e.g. bundled-features's set.write.ts) that
|
|
622
|
-
// can't afford a local wrapper reach for `ctx.
|
|
622
|
+
// can't afford a local wrapper reach for `ctx.unsafeAppendEvent`
|
|
623
623
|
// instead — same runtime, looser type-surface.
|
|
624
624
|
export type AppendEventFn<TMap extends object = KumikoEventTypeMap> = <K extends keyof TMap>(
|
|
625
625
|
args: TypedAppendEventArgs<TMap, K>,
|
|
626
626
|
) => Promise<void>;
|
|
627
627
|
|
|
628
|
-
export type
|
|
628
|
+
export type UnsafeAppendEventFn = (args: AppendEventArgs) => Promise<void>;
|
|
629
629
|
|
|
630
630
|
// Args for ctx.fetchForWriting — Marten FetchForWriting equivalent. Returns
|
|
631
631
|
// the current stream state + a handle that appends without re-specifying
|
|
@@ -737,8 +737,16 @@ export type WriteHandlerDef = {
|
|
|
737
737
|
readonly schema: ZodType;
|
|
738
738
|
readonly handler: WriteHandlerFn;
|
|
739
739
|
readonly access?: AccessRule;
|
|
740
|
-
readonly
|
|
740
|
+
readonly unsafeSkipTransitionGuard?: boolean;
|
|
741
741
|
readonly rateLimit?: RateLimitOption;
|
|
742
|
+
// Set when the author wrote a `perform: pipeline(...)` block. Boot-
|
|
743
|
+
// validators (projection-allowlist) and Designer/AI tooling read this
|
|
744
|
+
// to inspect the step list. Absent on free-form handlers.
|
|
745
|
+
// Inline-import is intentional: step.ts imports HandlerContext from
|
|
746
|
+
// this file, a top-level `import type { PipelineDef } from "./step"`
|
|
747
|
+
// would form a type-only circular import that TS resolves but tooling
|
|
748
|
+
// (incremental compile, IDEs) sometimes mis-handles.
|
|
749
|
+
readonly perform?: import("./step").PipelineDef;
|
|
742
750
|
};
|
|
743
751
|
|
|
744
752
|
export type QueryHandlerDef = {
|
|
@@ -102,7 +102,6 @@ export type {
|
|
|
102
102
|
AppContext,
|
|
103
103
|
AppendEventArgs,
|
|
104
104
|
AppendEventFn,
|
|
105
|
-
AppendEventUnsafeFn,
|
|
106
105
|
AuthClaimsContext,
|
|
107
106
|
AuthClaimsFn,
|
|
108
107
|
AuthClaimsHookDef,
|
|
@@ -133,6 +132,7 @@ export type {
|
|
|
133
132
|
RateLimitOption,
|
|
134
133
|
RateLimitPer,
|
|
135
134
|
SessionUser,
|
|
135
|
+
UnsafeAppendEventFn,
|
|
136
136
|
WriteEvent,
|
|
137
137
|
WriteHandlerDef,
|
|
138
138
|
WriteHandlerFn,
|
|
@@ -211,4 +211,15 @@ export type {
|
|
|
211
211
|
ToolbarAction,
|
|
212
212
|
} from "./screen";
|
|
213
213
|
export { normalizeEditField, normalizeListColumn } from "./screen";
|
|
214
|
+
export type { TargetRef } from "./target-ref";
|
|
215
|
+
export type {
|
|
216
|
+
Subscribe,
|
|
217
|
+
TreeAction,
|
|
218
|
+
TreeActionDef,
|
|
219
|
+
TreeActionsHandle,
|
|
220
|
+
TreeChildrenSubscribe,
|
|
221
|
+
TreeContext,
|
|
222
|
+
TreeNode,
|
|
223
|
+
TreeNodeState,
|
|
224
|
+
} from "./tree-node";
|
|
214
225
|
export type { WorkspaceDefinition } from "./workspace";
|