@cosmicdrift/kumiko-types 0.326.1 → 0.328.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.
Files changed (173) hide show
  1. package/dist/action-icon.d.ts +3 -0
  2. package/dist/action-icon.js +51 -0
  3. package/{src/agent-exposure.ts → dist/agent-exposure.d.ts} +8 -15
  4. package/dist/agent-exposure.js +16 -0
  5. package/dist/concurrency-mode.d.ts +1 -0
  6. package/dist/config-scope.d.ts +1 -0
  7. package/dist/config-scope.js +1 -0
  8. package/dist/config.d.ts +234 -0
  9. package/dist/config.js +1 -0
  10. package/dist/cursor-types.d.ts +16 -0
  11. package/dist/cursor-types.js +1 -0
  12. package/dist/db-connection.d.ts +14 -0
  13. package/dist/db-connection.js +1 -0
  14. package/dist/define-handler.d.ts +58 -0
  15. package/dist/define-handler.js +1 -0
  16. package/dist/derivatives-types.d.ts +53 -0
  17. package/dist/derivatives-types.js +3 -0
  18. package/dist/entity-cache.d.ts +16 -0
  19. package/dist/entity-cache.js +1 -0
  20. package/dist/entity-handlers.d.ts +54 -0
  21. package/dist/entity-handlers.js +1 -0
  22. package/dist/entity-table-meta-types.d.ts +44 -0
  23. package/dist/entity-table-meta-types.js +6 -0
  24. package/dist/envelope-cipher-types.d.ts +5 -0
  25. package/dist/envelope-cipher-types.js +1 -0
  26. package/dist/event-store-executor-types.d.ts +86 -0
  27. package/dist/event-store-executor-types.js +1 -0
  28. package/dist/event-store-types.d.ts +37 -0
  29. package/dist/event-store-types.js +7 -0
  30. package/dist/event-type-map.d.ts +8 -0
  31. package/{src/event-type-map.ts → dist/event-type-map.js} +1 -16
  32. package/dist/executor-brand.d.ts +8 -0
  33. package/dist/executor-brand.js +1 -0
  34. package/dist/extension-options-map.d.ts +9 -0
  35. package/dist/extension-options-map.js +4 -0
  36. package/dist/feature.d.ts +518 -0
  37. package/dist/feature.js +1 -0
  38. package/dist/field-icon.d.ts +1 -0
  39. package/dist/field-icon.js +1 -0
  40. package/dist/fields.d.ts +729 -0
  41. package/dist/fields.js +50 -0
  42. package/dist/file-handle-types.d.ts +12 -0
  43. package/dist/file-handle-types.js +1 -0
  44. package/dist/file-provider-resolver-types.d.ts +3 -0
  45. package/dist/file-provider-resolver-types.js +1 -0
  46. package/dist/file-storage-provider-types.d.ts +17 -0
  47. package/dist/file-storage-provider-types.js +1 -0
  48. package/dist/geo-tz.d.ts +17 -0
  49. package/{src/geo-tz.ts → dist/geo-tz.js} +1 -20
  50. package/dist/handlers.d.ts +493 -0
  51. package/dist/handlers.js +49 -0
  52. package/dist/hook-phase.d.ts +1 -0
  53. package/dist/hook-phase.js +1 -0
  54. package/dist/hooks.d.ts +75 -0
  55. package/dist/hooks.js +1 -0
  56. package/dist/http-route.d.ts +52 -0
  57. package/dist/http-route.js +13 -0
  58. package/dist/identifiers.d.ts +8 -0
  59. package/{src/identifiers.ts → dist/identifiers.js} +7 -24
  60. package/dist/index.d.ts +1 -0
  61. package/dist/index.js +1 -0
  62. package/dist/kms-adapter-types.d.ts +68 -0
  63. package/dist/kms-adapter-types.js +49 -0
  64. package/dist/logger.d.ts +7 -0
  65. package/dist/logger.js +1 -0
  66. package/dist/multi-stream-apply-context-types.d.ts +18 -0
  67. package/dist/multi-stream-apply-context-types.js +1 -0
  68. package/dist/nav-icon.d.ts +3 -0
  69. package/dist/nav-icon.js +100 -0
  70. package/dist/nav.d.ts +39 -0
  71. package/dist/nav.js +1 -0
  72. package/dist/observability/index.d.ts +3 -0
  73. package/dist/observability/index.js +2 -0
  74. package/dist/observability/metric.d.ts +34 -0
  75. package/dist/observability/metric.js +3 -0
  76. package/dist/observability/provider.d.ts +24 -0
  77. package/dist/observability/provider.js +4 -0
  78. package/dist/observability/span.d.ts +32 -0
  79. package/dist/observability/span.js +4 -0
  80. package/dist/ownership.d.ts +33 -0
  81. package/dist/ownership.js +5 -0
  82. package/dist/projection.d.ts +38 -0
  83. package/dist/projection.js +1 -0
  84. package/dist/rate-limit-types.d.ts +19 -0
  85. package/dist/rate-limit-types.js +1 -0
  86. package/dist/relations.d.ts +25 -0
  87. package/dist/relations.js +1 -0
  88. package/dist/schema-table-types.d.ts +15 -0
  89. package/{src/schema-table-types.ts → dist/schema-table-types.js} +0 -20
  90. package/dist/screen.d.ts +1265 -0
  91. package/dist/screen.js +3 -0
  92. package/dist/search-adapter.d.ts +32 -0
  93. package/dist/search-adapter.js +1 -0
  94. package/dist/secrets-types.d.ts +52 -0
  95. package/dist/secrets-types.js +30 -0
  96. package/dist/snapshot-types.d.ts +13 -0
  97. package/dist/snapshot-types.js +1 -0
  98. package/dist/step.d.ts +304 -0
  99. package/dist/step.js +11 -0
  100. package/dist/target-ref.d.ts +5 -0
  101. package/{src/target-ref.ts → dist/target-ref.js} +1 -6
  102. package/dist/tenancy-brand.d.ts +6 -0
  103. package/dist/tenancy-brand.js +1 -0
  104. package/dist/tenant-db-types.d.ts +67 -0
  105. package/dist/tenant-db-types.js +5 -0
  106. package/dist/tree-node.d.ts +26 -0
  107. package/dist/tree-node.js +22 -0
  108. package/dist/tz-context.d.ts +48 -0
  109. package/dist/tz-context.js +6 -0
  110. package/dist/websocket-route.d.ts +46 -0
  111. package/dist/websocket-route.js +10 -0
  112. package/dist/where-clause-types.d.ts +20 -0
  113. package/dist/where-clause-types.js +1 -0
  114. package/dist/workspace.d.ts +10 -0
  115. package/dist/workspace.js +1 -0
  116. package/dist/write-error-info-types.d.ts +12 -0
  117. package/dist/write-error-info-types.js +1 -0
  118. package/package.json +362 -119
  119. package/src/changes.json +12 -0
  120. package/src/__tests__/handlers.test.ts +0 -40
  121. package/src/__tests__/plural-forms.test.ts +0 -18
  122. package/src/action-icon.ts +0 -57
  123. package/src/concurrency-mode.ts +0 -5
  124. package/src/config-scope.ts +0 -5
  125. package/src/config.ts +0 -563
  126. package/src/cursor-types.ts +0 -18
  127. package/src/db-connection.ts +0 -25
  128. package/src/define-handler.ts +0 -138
  129. package/src/derivatives-types.ts +0 -106
  130. package/src/entity-cache.ts +0 -35
  131. package/src/entity-handlers.ts +0 -68
  132. package/src/entity-table-meta-types.ts +0 -101
  133. package/src/envelope-cipher-types.ts +0 -6
  134. package/src/event-store-executor-types.ts +0 -163
  135. package/src/event-store-types.ts +0 -85
  136. package/src/executor-brand.ts +0 -19
  137. package/src/extension-options-map.ts +0 -21
  138. package/src/feature.ts +0 -1132
  139. package/src/field-icon.ts +0 -23
  140. package/src/fields.ts +0 -1114
  141. package/src/file-handle-types.ts +0 -20
  142. package/src/file-provider-resolver-types.ts +0 -7
  143. package/src/file-storage-provider-types.ts +0 -54
  144. package/src/handlers.ts +0 -1246
  145. package/src/hook-phase.ts +0 -2
  146. package/src/hooks.ts +0 -170
  147. package/src/http-route.ts +0 -78
  148. package/src/kms-adapter-types.ts +0 -128
  149. package/src/logger.ts +0 -7
  150. package/src/multi-stream-apply-context-types.ts +0 -52
  151. package/src/nav-icon.ts +0 -105
  152. package/src/nav.ts +0 -139
  153. package/src/observability/index.ts +0 -28
  154. package/src/observability/metric.ts +0 -56
  155. package/src/observability/provider.ts +0 -32
  156. package/src/observability/span.ts +0 -58
  157. package/src/ownership.ts +0 -83
  158. package/src/projection.ts +0 -170
  159. package/src/rate-limit-types.ts +0 -32
  160. package/src/relations.ts +0 -52
  161. package/src/screen.ts +0 -1593
  162. package/src/search-adapter.ts +0 -45
  163. package/src/secrets-types.ts +0 -182
  164. package/src/snapshot-types.ts +0 -36
  165. package/src/step.ts +0 -390
  166. package/src/tenancy-brand.ts +0 -9
  167. package/src/tenant-db-types.ts +0 -116
  168. package/src/tree-node.ts +0 -134
  169. package/src/tz-context.ts +0 -63
  170. package/src/where-clause-types.ts +0 -31
  171. package/src/workspace.ts +0 -42
  172. package/src/write-error-info-types.ts +0 -20
  173. /package/{src/index.ts → dist/concurrency-mode.js} +0 -0
@@ -1,45 +0,0 @@
1
- import type { EntityId, TenantId } from "./identifiers";
2
-
3
- export type SearchAdapterConfig = {
4
- searchableFields: readonly string[];
5
- rankingFields?: readonly string[];
6
- };
7
-
8
- export type SearchDocument = {
9
- entityType: string;
10
- entityId: EntityId;
11
- weight: number;
12
- fields: Record<string, unknown>;
13
- };
14
-
15
- export type SearchResult = {
16
- entityType: string;
17
- entityId: EntityId;
18
- };
19
-
20
- export type SearchOptions = {
21
- limit?: number;
22
- filterType?: string;
23
- };
24
-
25
- export type SearchAdapter = {
26
- configure(tenantId: TenantId, config: SearchAdapterConfig): Promise<void>;
27
- // The adapter is built before the registry exists (app boot order), so it
28
- // can't derive this itself. The framework hands it the registry-derived
29
- // config once the registry is available; the adapter uses it to configure
30
- // each tenant index lazily on that tenant's first access instead of
31
- // requiring every app to call `configure` per tenant up front.
32
- setDefaultConfig?(config: SearchAdapterConfig): void;
33
- index(tenantId: TenantId, doc: SearchDocument): Promise<void>;
34
- search(tenantId: TenantId, query: string, options?: SearchOptions): Promise<SearchResult[]>;
35
- remove(tenantId: TenantId, entityType: string, entityId: EntityId): Promise<void>;
36
- // Bulk variants. Default implementations loop over the single-doc methods —
37
- // adapters should override when the backend supports a real batch call
38
- // (Meilisearch, Elasticsearch, Typesense all do). Cuts a batch-write from
39
- // N sequential HTTP + waitTask round-trips to one.
40
- indexBatch?(tenantId: TenantId, docs: readonly SearchDocument[]): Promise<void>;
41
- removeBatch?(
42
- tenantId: TenantId,
43
- items: readonly { entityType: string; entityId: EntityId }[],
44
- ): Promise<void>;
45
- };
@@ -1,182 +0,0 @@
1
- // Envelope Encryption types. Separating DEK (per-value) from KEK (central)
2
- // is what makes key rotation cheap: on rotation we only re-wrap the small
3
- // encryptedDek, never touch the ciphertext.
4
-
5
- import type { TenantId } from "./identifiers";
6
-
7
- // Plaintext-secret wrapper (branded). Carries the actual string internally
8
- // but the nominal typing stops it from landing in an HTTP response by
9
- // accident — a response-serializer guard + the reveal() cost make the leak
10
- // intentional. Framework code that sees `Secret<string>` knows the caller
11
- // has already gone through the audited ctx.secrets.get path.
12
- //
13
- // Registered symbol, matching KUMIKO_*_SYMBOL in schema-table-types.ts. A
14
- // plain Symbol() is per-copy: two resolved copies of this package brand with
15
- // two different symbols, so createSecret() from one and isSecret() from the
16
- // other disagree — and isSecret() is the ONLY thing assertNoSecretLeak has,
17
- // so a false there serializes the plaintext into the response. The namespaced
18
- // key makes accidental user-land collision implausible; a deliberate forge
19
- // would only over-trigger the guard, which fails safe.
20
- const SecretBrand: unique symbol = Symbol.for("kumiko.secret");
21
-
22
- export type Secret<T = string> = {
23
- readonly [SecretBrand]: true;
24
- readonly reveal: () => T;
25
- };
26
-
27
- // Implementation helper — bundled-features uses this to wrap a plaintext after
28
- // decryption. Kept in the framework so both sides share one canonical brand.
29
- export function createSecret<T>(value: T): Secret<T> {
30
- return {
31
- [SecretBrand]: true as const,
32
- reveal: () => value,
33
- };
34
- }
35
-
36
- // True for any object carrying the Secret brand. Used by the response guard
37
- // to reject leaks before serialization.
38
- export function isSecret(v: unknown): v is Secret<unknown> {
39
- return typeof v === "object" && v !== null && SecretBrand in v;
40
- }
41
-
42
- // --- Compile-time response guard (R6) --------------------------------------
43
- //
44
- // ContainsSecret<T> is `true` only when a Secret<> is DEFINITELY present
45
- // somewhere in T. The handler-registration guard (defineWriteHandler/
46
- // defineQueryHandler) turns a `true` into a compile error — the static twin of
47
- // assertNoSecretLeak's runtime walk.
48
- //
49
- // Biased to `false`: anything it cannot inspect — a bare generic type param (a
50
- // handler generic over its response), `unknown`/`any`, `never` — resolves to
51
- // `false` = allowed, with the runtime guard as the backstop. The alternative
52
- // (default-to-leak) false-flags every legitimate generic-over-response handler.
53
- //
54
- // Branch order is load-bearing: never/unknown/any first (uninspectable), then
55
- // Secret, then primitives (covers branded primitives like TenantId without
56
- // enumerating them), then the SafeLeaf allowlist (opaque class instances that
57
- // blind `{ [K in keyof T] }` recursion would mangle — the type-level mirror of
58
- // leak-guard.ts skipping non-plain objects), then arrays, then a "does any
59
- // field contain a secret" fold over plain objects.
60
- type Primitive = string | number | boolean | bigint | symbol | null | undefined;
61
-
62
- // Opaque built-in leaves a response legitimately carries; never recurse into
63
- // them. Extend when the bundled-features tsc sweep surfaces a real leaf type.
64
- // Map/Set (556/2): `keyof Map<K,V>` yields method names, not V, so these
65
- // already fell through to `false` via the object-mapped-type branch — listed
66
- // explicitly here so the compile-time treatment matches leak-guard.ts's
67
- // runtime `instanceof Map`/`instanceof Set` branch (walk entries separately)
68
- // instead of looking like an oversight.
69
- type SafeLeaf =
70
- | Date
71
- | RegExp
72
- | Temporal.Instant
73
- | Temporal.ZonedDateTime
74
- | Temporal.PlainDate
75
- | Temporal.PlainDateTime
76
- | Temporal.PlainTime
77
- | Temporal.PlainYearMonth
78
- | Temporal.PlainMonthDay
79
- | Temporal.Duration
80
- | Map<unknown, unknown>
81
- | Set<unknown>;
82
-
83
- export type ContainsSecret<T> = [T] extends [never]
84
- ? false
85
- : unknown extends T
86
- ? false
87
- : T extends Secret<unknown>
88
- ? true
89
- : T extends Primitive
90
- ? false
91
- : T extends SafeLeaf
92
- ? false
93
- : T extends readonly (infer U)[]
94
- ? ContainsSecret<U>
95
- : T extends object
96
- ? true extends { [K in keyof T]-?: ContainsSecret<T[K]> }[keyof T]
97
- ? true
98
- : false
99
- : false;
100
-
101
- // Per-read audit context. Populated by requireSecretsContext() wrapper so
102
- // handlers don't need to pass userId/handlerName manually on every call.
103
- // Undefined for framework-internal reads (rotation job, tests) — the audit
104
- // table stays a "who touched this credential" log, not a crash-report sink.
105
- export type SecretAuditContext = {
106
- readonly userId: string;
107
- readonly handlerName: string;
108
- };
109
-
110
- // Feature code can pass either the raw qualified-name string or a typed
111
- // handle returned by r.secret. The handle form is safer — renaming the
112
- // r.secret call updates all references through the import graph.
113
- export type SecretKeyRef = string | { readonly name: string };
114
-
115
- // The ctx.secrets contract. Concrete implementation lives in bundled-features
116
- // (createSecretsContext) where the DB and MasterKeyProvider are known. This
117
- // lean interface is what the framework's HandlerContext carries so engine
118
- // code can talk about it without pulling in bundled-features.
119
- export interface SecretsContext {
120
- get(
121
- tenantId: TenantId,
122
- key: SecretKeyRef,
123
- auditCtx?: SecretAuditContext,
124
- ): Promise<Secret<string> | undefined>;
125
- // Metadata-only existence probe: no decryption, no read-audit event.
126
- // For readiness checks — use get() when the value itself is needed.
127
- has(tenantId: TenantId, key: SecretKeyRef): Promise<boolean>;
128
- set(
129
- tenantId: TenantId,
130
- key: SecretKeyRef,
131
- value: string,
132
- opts?: { redact?: (plaintext: string) => string; hint?: string; updatedBy?: string },
133
- ): Promise<void>;
134
- delete(tenantId: TenantId, key: SecretKeyRef, opts?: { deletedBy?: string }): Promise<boolean>;
135
- }
136
-
137
- export type Envelope = {
138
- // AES-256-GCM ciphertext of the plaintext, keyed with a DEK.
139
- readonly ciphertext: Buffer;
140
- // GCM nonce (12 bytes). Generated fresh per encryption.
141
- readonly iv: Buffer;
142
- // GCM auth tag (16 bytes). Guarantees the ciphertext wasn't tampered.
143
- readonly authTag: Buffer;
144
- // DEK wrapped with the current KEK. Decryption needs provider.unwrapDek
145
- // with the kekVersion to recover the DEK.
146
- readonly encryptedDek: Buffer;
147
- // Which KEK version was used to wrap the DEK. On rotation, rows with old
148
- // versions still decrypt — the provider keeps a keyring of historical KEKs.
149
- readonly kekVersion: number;
150
- };
151
-
152
- // BYOK hook: callers pass the tenant a value belongs to; a per-tenant-KMS
153
- // provider keys its wrap/unwrap on it. EnvMasterKeyProvider (app-wide
154
- // keyring) ignores it — the param exists so the contract doesn't have to
155
- // break when a tenant-scoped provider ships.
156
- export type KeyScope = {
157
- readonly tenantId?: TenantId;
158
- };
159
-
160
- // The contract a KEK backend must fulfil. The framework sees only this
161
- // interface; concrete implementations live in separate packages
162
- // (@cosmicdrift/kumiko-secrets-vault, @cosmicdrift/kumiko-secrets-aws-kms, ...). The default is
163
- // EnvMasterKeyProvider which reads keys from environment variables.
164
- export interface MasterKeyProvider {
165
- // Wrap a fresh DEK with the current KEK. Returns the wrapped bytes + the
166
- // KEK version used — the version ends up in the Envelope so decryption
167
- // later knows which KEK to ask for.
168
- wrapDek(dek: Buffer, scope?: KeyScope): Promise<{ encryptedDek: Buffer; kekVersion: number }>;
169
-
170
- // Unwrap a previously-wrapped DEK. During rotation the provider must
171
- // accept older kekVersion values (2-version window minimum), otherwise
172
- // old rows become unreadable.
173
- unwrapDek(encryptedDek: Buffer, kekVersion: number, scope?: KeyScope): Promise<Buffer>;
174
-
175
- // Which KEK version new wraps use. Rotation flips this to a new value
176
- // and older-version reads continue to work until rows are re-wrapped.
177
- currentVersion(): number;
178
-
179
- // Health check: can the provider talk to its backend? Used by
180
- // /health/ready. Cheap probe, no KEK material read.
181
- isAvailable(): Promise<boolean>;
182
- }
@@ -1,36 +0,0 @@
1
- import type { StoredEvent } from "./event-store-types";
2
-
3
- // Reducer used to fold events onto a state. Kept narrow and pure — the
4
- // caller supplies the shape and update rules. Mirrors the reducer shape
5
- // feature authors already write for r.projection.apply.
6
- export type SnapshotReducer<TState extends Record<string, unknown>> = (
7
- state: TState,
8
- event: StoredEvent,
9
- ) => TState;
10
-
11
- export type LoadAggregateWithSnapshotResult<TState extends Record<string, unknown>> = {
12
- readonly state: TState;
13
- readonly version: number;
14
- readonly snapshotHit: boolean;
15
- };
16
-
17
- export type LoadAggregateWithSnapshotOptions = {
18
- // Opt-in: include archived streams in the rehydrate. Default false — same
19
- // semantics as loadAggregate / loadAggregateAsOf. Archive check is a
20
- // single indexed lookup, so the cost stays negligible on the hot path.
21
- readonly includeArchived?: boolean;
22
- // Optional upcaster step: every delta event goes through this transform
23
- // BEFORE the reducer sees it. The dispatcher wires this up with
24
- // r.eventMigration so feature code always sees current-version payloads.
25
- // Async to support Marten-style AsyncOnlyEventUpcaster (DB lookups).
26
- readonly upcastEvent?: (event: StoredEvent) => Promise<StoredEvent>;
27
- // Auto-snapshot policy: when the fold applied at least this many delta
28
- // events, persist a fresh snapshot at the folded version (best-effort —
29
- // a failed save never fails the load). Omit to keep snapshotting manual.
30
- readonly snapshotEvery?: number;
31
- // Reducer-shape generation stamped onto saved snapshots (default 1). A
32
- // stored snapshot with a different generation is ignored — full replay
33
- // through the upcaster chain — and restamped on the next auto-save. Bump
34
- // whenever the reducer's state shape changes.
35
- readonly snapshotVersion?: number;
36
- };
package/src/step.ts DELETED
@@ -1,390 +0,0 @@
1
- // Step-Vocabulary Types — see docs/plans/architecture/intern/step-vocabulary.md
2
- //
3
- // M.1 minimal scope:
4
- // - Steps execute against the existing HandlerContext (no per-step subset).
5
- // - steps-accumulator is Record<string, unknown> (no tuple-reduce typing).
6
- // - Resolvers receive the full PipelineCtx as one argument.
7
- //
8
- // Strict typing on appendEvent inside steps is deferred to a later pass
9
- // (see TS-typing notes in the design doc). M.1 uses unsafeAppendEvent
10
- // semantics under the hood for r.step.aggregate.appendEvent.
11
-
12
- import type { EventStoreExecutor } from "./event-store-executor-types";
13
- import type { KumikoEventTypeMap } from "./event-type-map";
14
- import type { HandlerContext, WriteEvent, WriteResult } from "./handlers";
15
- import type { SaveContext } from "./hooks";
16
- import type { EntityId } from "./identifiers";
17
- import type { WhereObject } from "./where-clause-types";
18
-
19
- /**
20
- * The kind discriminator for a step instance — matches the step's
21
- * registration name in the step-registry (e.g. "return", "compute",
22
- * "aggregate.create"). Steps register themselves at module-load time
23
- * via defineStep().
24
- */
25
- export type StepKind = string;
26
-
27
- /**
28
- * Pipeline-side context handed to step argument resolvers.
29
- *
30
- * Contains the full HandlerContext (no per-step subset in M.1) plus
31
- * the accumulated `steps` map of prior step results, and a `scope`
32
- * record for forEach/branch-local bindings.
33
- *
34
- * `scope` is `Record<string, unknown>` — sub-step builders (forEach,
35
- * branch) populate it once they land in later M.1 slices. M.1.1 ships
36
- * with `r.step.return` only, which reads `event` and ignores both
37
- * `steps` and `scope`.
38
- */
39
- export type PipelineCtx<
40
- TPayload = unknown,
41
- TMap extends object = KumikoEventTypeMap,
42
- > = HandlerContext<TMap> & {
43
- readonly event: WriteEvent<TPayload>;
44
- readonly steps: Readonly<Record<string, unknown>>;
45
- readonly scope: Readonly<Record<string, unknown>>;
46
- // Workflow-run context — present only when running inside defineWorkflow.
47
- // Tier-3 steps use this to write suspension events onto the correct
48
- // aggregate stream and for the Resume-Loop to re-hydrate state.
49
- readonly workflow?: {
50
- readonly runId: string;
51
- readonly workflowName: string;
52
- /** Current step index in the pipeline — set by the executor before each step. */
53
- readonly stepIndex: number;
54
- /**
55
- * Retry attempt counter for the current workflow.retry step.
56
- * Set by the workflow-engine resume-loop on re-entry; starts at 1.
57
- * Absent (undefined) means first attempt.
58
- */
59
- readonly retryAttempt?: number;
60
- /**
61
- * Q7 Snapshot-at-Start fingerprint of the workflow definition that
62
- * started this run. Propagated into every suspension event payload so
63
- * the resume-loop can detect library-upgrades that changed the closure
64
- * source and surface them as a typed failure instead of running a
65
- * stale-vs-new mix of steps. Optional only for legacy callers; the
66
- * workflow-runner sets it on every newly-started run.
67
- */
68
- readonly definitionFingerprint?: string;
69
- };
70
- };
71
-
72
- /**
73
- * A resolver is either a static value or a function that derives the
74
- * value from the pipeline-context. M.1 keeps the resolver signature
75
- * uniform — every step accepts both forms via a normalise helper.
76
- */
77
- export type StepResolver<T, TPayload = unknown> = T | ((ctx: PipelineCtx<TPayload>) => T);
78
-
79
- // Branded event-type string — the only route r.step.waitForEvent's `event`
80
- // can reach a value. A workflow's `awaits` declaration (D4,
81
- // workflow-resume-loop.md) is the only source of these; buildPipelineSteps
82
- // mints them from that declaration's raw strings when it builds the
83
- // closure's `awaits` binding. A raw string literal at waitForEvent.event is
84
- // a compile error, so a typo in the awaited key surfaces at build time
85
- // instead of a run that waits forever.
86
- export type AwaitedEventType = string & { readonly __awaitedEvent: unique symbol };
87
-
88
- // Serializable AST for r.step.waitForEvent's `match` argument. Persisted
89
- // verbatim into the waiting-for-event suspension payload (jsonb), so it
90
- // must survive a JSON round-trip — a closure cannot.
91
- export type EventMatchValue = string | number | boolean | null;
92
-
93
- export type EventMatchOp =
94
- | { readonly kind: "eq"; readonly value: EventMatchValue }
95
- | { readonly kind: "ne"; readonly value: EventMatchValue }
96
- | { readonly kind: "in"; readonly values: readonly EventMatchValue[] }
97
- | { readonly kind: "gt"; readonly value: number | string }
98
- | { readonly kind: "gte"; readonly value: number | string }
99
- | { readonly kind: "lt"; readonly value: number | string }
100
- | { readonly kind: "lte"; readonly value: number | string };
101
-
102
- export type EventMatchExpr =
103
- | { readonly kind: "and"; readonly nodes: readonly EventMatchExpr[] }
104
- | { readonly kind: "or"; readonly nodes: readonly EventMatchExpr[] }
105
- | { readonly kind: "atom"; readonly path: readonly string[]; readonly op: EventMatchOp };
106
-
107
- export type EventMatch = { readonly version: 1; readonly expr: EventMatchExpr };
108
-
109
- /**
110
- * Per-step error strategy. M.1.1 only supports "throw" — the type is
111
- * deliberately narrowed so callers cannot pass an unsupported strategy
112
- * past the type-checker. The doc lists "return" / "skip" / fallback as
113
- * future strategies; each lands together with its own runtime support
114
- * + integration test in a later slice (no untested type expansion).
115
- */
116
- export type StepFailureStrategy = "throw";
117
-
118
- export type StepDef<TArgs = unknown, TResult = unknown> = {
119
- readonly kind: StepKind;
120
- readonly defaultFailureStrategy: StepFailureStrategy;
121
- // Returns the result-key for this step instance, or undefined when the
122
- // step doesn't surface a result. The first-position name on the call
123
- // (e.g. r.step.compute("startedAt", fn) → "startedAt") becomes the key.
124
- readonly resultKey?: (args: TArgs) => string | undefined;
125
- // Sub-pipeline arg-paths — names of `args.<path>` entries that hold a
126
- // readonly StepInstance[] (e.g. branch's `["onTrue", "onFalse"]`, forEach's
127
- // `["do"]`). The boot-validator reads these at registration time so it
128
- // can recurse into nested pipelines without a hardcoded kind-list. Steps
129
- // that don't carry sub-pipelines omit the field. Followup #15 self-
130
- // registration: prevents future sub-step-builders from silently bypassing
131
- // the unsafeProjection allowlist by forgetting to update a central map.
132
- readonly subPaths?: readonly string[];
133
- // Step-vocabulary tier (Q9). Tier-1 implicit, Tier-2+ requires
134
- // r.requires.step("<kind>") in the owning feature. Default 1 (implicit).
135
- // Tier-3 is only available inside defineWorkflow.
136
- readonly tier?: 1 | 2 | 3;
137
- // Runtime: resolve the args against the ctx, perform the work, return
138
- // the value to land in steps.{resultKey}. Thrown errors propagate to
139
- // the dispatcher's catch (M.1.1 supports "throw"-strategy only).
140
- readonly run: (args: TArgs, ctx: PipelineCtx) => Promise<TResult> | TResult;
141
- };
142
-
143
- /**
144
- * An instance of a step in a pipeline — what users build via the
145
- * step-builder (`r.step.compute(...)`). Carries the kind + resolved-or-
146
- * resolver args + the user-chosen onFailure override.
147
- *
148
- * `args` is `unknown` at this layer — the registered StepDef knows the
149
- * concrete shape and casts at run() time. Cross-step type-safety lives
150
- * in the per-step builder factories, not in this central type.
151
- */
152
- export type StepInstance = {
153
- readonly kind: StepKind;
154
- readonly args: unknown;
155
- readonly onFailure?: StepFailureStrategy;
156
- };
157
-
158
- /**
159
- * What `stepsPipeline(closure)` returns. Carries the closure (instead of an
160
- * eagerly-built array) so each handler-call sees a fresh event ref and
161
- * the `r` step-builder is resolved at runtime, not at module-load time.
162
- *
163
- * `__kind: "pipeline"` lets defineWriteHandler distinguish a pipeline-
164
- * form `perform` from accidental other shapes.
165
- *
166
- * `_TData` is a phantom type-parameter — held in constraint position
167
- * only, never referenced in the type body. defineWriteHandler binds it
168
- * via `def.perform: PipelineDef<…, TData>` (the call-site uses TData
169
- * without underscore — phantom-prefix is purely a Biome
170
- * `noUnusedVariables` marker, not user-facing). _TData is NOT inferred
171
- * from the closure body (r.step.return has its own per-call TData), so
172
- * callers must spell it explicitly:
173
- * `stepsPipeline<{ greeting: string }, { echoed: string }>(...)`
174
- * Better DX is a known follow-up — see step-vocabulary.md M.1-Followups.
175
- */
176
- export type PipelineDef<
177
- TPayload = unknown,
178
- _TData = unknown,
179
- TAwaits extends Record<string, AwaitedEventType> = Record<string, AwaitedEventType>,
180
- > = {
181
- readonly __kind: "pipeline";
182
- // Raw (unbranded) awaits map, set by defineWorkflow — absent for
183
- // non-workflow pipelines (plain defineWriteHandler perform).
184
- // buildPipelineSteps reads it and brands it into the closure's `awaits`
185
- // binding, so every existing buildPipelineSteps caller keeps working
186
- // unchanged. Deliberately typed as plain strings, NOT `TAwaits` — an
187
- // extra covariant occurrence of TAwaits here (alongside its contravariant
188
- // occurrence in `build`'s parameter) breaks TypeScript's contextual
189
- // inference for a `stepsPipeline(...)` passed inline as `steps` (verified
190
- // empirically; see fw#2513 Phase 3a).
191
- readonly awaits?: Readonly<Record<string, string>>;
192
- readonly build: (ctx: PipelineBuildCtx<TPayload, TAwaits>) => readonly StepInstance[];
193
- };
194
-
195
- /**
196
- * Argument bundle passed to the closure inside `stepsPipeline(closure)`.
197
- * Build-time only — no `steps`, no `scope`, no `db`: at build time no
198
- * step has run yet.
199
- *
200
- * The closure is invoked ONCE per handler-call and returns the immutable
201
- * list of step instances. Values inside the closure that depend on prior
202
- * step results MUST go through resolvers (functions) — those receive the
203
- * resolver-side PipelineCtx which carries `steps` + `scope`.
204
- *
205
- * **Closure-body contract:** the closure must produce a deterministic
206
- * step-list that doesn't depend on `event.payload` — branching on payload
207
- * fields belongs inside resolvers (where they fire per-call), not in the
208
- * outer closure body. Boot-validation runs the closure once with a dummy
209
- * empty payload to scan unsafeProjection-* step targets; a closure that
210
- * conditionally builds different step-lists per payload would silently
211
- * skip validation. See validate-projection-allowlist.ts for the
212
- * boot-side mechanics.
213
- */
214
- export type PipelineBuildCtx<
215
- TPayload = unknown,
216
- TAwaits extends Record<string, AwaitedEventType> = Record<string, AwaitedEventType>,
217
- > = {
218
- readonly event: WriteEvent<TPayload>;
219
- readonly r: StepBuilder;
220
- // Declared awaited events (D4, workflow-resume-loop.md) — the only way
221
- // r.step.waitForEvent's `event` arg can reference an event type. `{}` for
222
- // non-workflow pipelines and workflows without an `awaits` declaration.
223
- readonly awaits: TAwaits;
224
- };
225
-
226
- /**
227
- * Step-builder namespace handed to the pipeline closure. The fields
228
- * grow as M.1 adds more steps. Each is a thin factory that returns a
229
- * StepInstance — the runtime resolution happens later in run().
230
- *
231
- * Why nested under `step`: matches the doc-API surface and leaves room
232
- * for future sibling namespaces (`r.trigger`, `r.transform`) without
233
- * crowding the top-level r.
234
- */
235
- export type StepBuilder = {
236
- readonly step: StepNamespace;
237
- };
238
-
239
- /**
240
- * The collection of step factory functions. Grown incrementally —
241
- * landed: return (M.1.1), compute (M.1.2), unsafeProjectionUpsert
242
- * (M.1.3), aggregate.create (M.1.4). Pending: branch, forEach,
243
- * read.*, aggregate.update, aggregate.appendEvent,
244
- * unsafeProjectionDelete.
245
- */
246
- export type StepNamespace = {
247
- readonly return: <TData>(resolver: StepResolver<WriteResult<TData>>) => StepInstance;
248
- readonly compute: <TResult>(name: string, fn: (ctx: PipelineCtx) => TResult) => StepInstance;
249
- // Inline read-side projection write. Boot-validation enforces the
250
- // table is in the owning feature's r.requires.projection allowlist
251
- // and NOT registered as an aggregate-table via r.entity. See
252
- // step-vocabulary.md "Was unsafeProjection.* überspringt".
253
- readonly unsafeProjectionUpsert: (args: {
254
- readonly table: unknown;
255
- readonly on: readonly string[];
256
- readonly row: StepResolver<Record<string, unknown>>;
257
- }) => StepInstance;
258
- // Sibling: delete row(s) from a read-side projection table. Same
259
- // boot-validation contract as unsafeProjectionUpsert.
260
- readonly unsafeProjectionDelete: (args: {
261
- readonly table: unknown;
262
- readonly where: StepResolver<WhereObject>;
263
- }) => StepInstance;
264
- // Read sub-namespace — thin wrapper on selectMany/fetchOne (bun-db).
265
- // Tenant-filtered like ctx.db; unsafeAllTenants needs escapeHatch on the handler (or systemScope).
266
- readonly read: {
267
- readonly findOne: (
268
- name: string,
269
- opts: {
270
- readonly table: unknown;
271
- readonly where: StepResolver<WhereObject | undefined>;
272
- readonly unsafeAllTenants?: { readonly reason: string };
273
- },
274
- ) => StepInstance;
275
- readonly findMany: (
276
- name: string,
277
- opts: {
278
- readonly table: unknown;
279
- readonly where?: StepResolver<WhereObject | undefined>;
280
- readonly limit?: number;
281
- readonly unsafeAllTenants?: { readonly reason: string };
282
- },
283
- ) => StepInstance;
284
- };
285
- // Aggregate-mutation sub-namespace — wraps the existing event-store-
286
- // executor surface. Every method goes through the full ES pipeline
287
- // (events + projections + lifecycle hooks + audit). The default and
288
- // intended path for domain mutation; contrast with unsafeProjection.*.
289
- readonly aggregate: {
290
- readonly create: (
291
- name: string,
292
- opts: {
293
- readonly executor: EventStoreExecutor;
294
- readonly data: StepResolver<Record<string, unknown>>;
295
- },
296
- ) => StepInstance;
297
- readonly update: (
298
- name: string,
299
- opts: {
300
- readonly executor: EventStoreExecutor;
301
- readonly id: StepResolver<EntityId>;
302
- readonly changes: StepResolver<Record<string, unknown>>;
303
- readonly version?: StepResolver<number | undefined>;
304
- readonly skipOptimisticLock?: boolean;
305
- },
306
- ) => StepInstance;
307
- readonly appendEvent: (args: {
308
- readonly aggregateId: StepResolver<string>;
309
- readonly aggregateType: string;
310
- readonly type: string;
311
- readonly payload: StepResolver<unknown>;
312
- readonly headers?: StepResolver<Readonly<Record<string, string | number | boolean>>>;
313
- }) => StepInstance;
314
- };
315
- // Conditional sub-pipeline. `onTrue` (required) and `onFalse`
316
- // (optional) are static StepInstance arrays; `r` for sub-step builders
317
- // is captured from the outer pipeline closure. Naming-Q14: `onTrue`/
318
- // `onFalse` over `then`/`else` because Biome's noThenProperty lint
319
- // flags `then` as a thenable-trap. Q12: r.step.return inside
320
- // onTrue/onFalse is rejected at build time (would trigger
321
- // discriminated-union TData trap). Q13: no resultKey — branch is
322
- // side-effect-only.
323
- readonly branch: (args: {
324
- readonly if: StepResolver<boolean>;
325
- readonly onTrue: readonly StepInstance[];
326
- readonly onFalse?: readonly StepInstance[];
327
- }) => StepInstance;
328
- // Iterate a sub-pipeline over an array. `as` is required (Q15);
329
- // current item lands under `scope[as]` for resolvers in `do`.
330
- // Sequential only in M.1.6; concurrency is Followup #12.
331
- readonly forEach: <TItem = unknown>(args: {
332
- readonly over: StepResolver<readonly TItem[]>;
333
- readonly as: string;
334
- readonly do: readonly StepInstance[];
335
- readonly concurrency?: 1;
336
- }) => StepInstance;
337
- // Tier-2 namespace. Each builder requires r.requires.step("<kind>")
338
- // in the owning feature; boot-validation enforces.
339
- readonly webhook: {
340
- readonly send: (args: {
341
- readonly url: StepResolver<string>;
342
- readonly method?: "POST" | "PUT" | "PATCH";
343
- readonly headers?: StepResolver<Readonly<Record<string, string>>>;
344
- readonly body?: StepResolver<unknown>;
345
- // Name inside the tenant-owned secrets namespace
346
- // `step-dispatcher:webhook-auth.<secret>` (secrets feature). Resolved
347
- // per-tenant at dispatch time — never a global/env-scoped secret.
348
- readonly auth?:
349
- | { readonly kind: "bearer"; readonly secret: string }
350
- | { readonly kind: "header"; readonly name: string; readonly secret: string };
351
- readonly mode: "deferred";
352
- readonly retry?: { readonly times: number; readonly backoff: "exponential" | "linear" };
353
- }) => StepInstance;
354
- };
355
- readonly mail: {
356
- readonly send: (args: {
357
- readonly to: StepResolver<string | readonly string[]>;
358
- readonly subject: StepResolver<string>;
359
- readonly body: StepResolver<string>;
360
- readonly from?: StepResolver<string>;
361
- readonly mode: "deferred";
362
- }) => StepInstance;
363
- };
364
- readonly callFeature: (
365
- name: string,
366
- opts: {
367
- readonly handler: string;
368
- readonly payload: StepResolver<unknown>;
369
- readonly as?: import("./handlers").SessionUser;
370
- },
371
- ) => StepInstance;
372
- // --- Tier-3 / Workflow-only steps ---
373
- // Only available inside defineWorkflow ({ steps: stepsPipeline(...) }).
374
- // Runtime guard: throws when used inside sync defineWriteHandler.
375
- readonly wait: (args: { readonly for: StepResolver<string> }) => StepInstance;
376
- readonly waitForEvent: (args: {
377
- readonly event: AwaitedEventType;
378
- readonly match?: StepResolver<EventMatch>;
379
- readonly timeout: StepResolver<string>;
380
- }) => StepInstance;
381
- readonly retry: (args: {
382
- readonly times: number;
383
- readonly backoff: "exponential" | "linear";
384
- readonly do: readonly StepInstance[];
385
- }) => StepInstance;
386
- };
387
-
388
- // SaveContext is the result-type of aggregate.create / aggregate.update;
389
- // re-exported for step authors who want to type their resolver bindings.
390
- export type AggregateStepResult = SaveContext;
@@ -1,9 +0,0 @@
1
- // Makes `db.global()` on a "tenant" table a compile error, mirroring executor-brand.ts's
2
- // approach for the executor-only write path.
3
- export type EntityTenancy = "global" | "tenant";
4
-
5
- declare const TENANCY: unique symbol;
6
-
7
- export interface TenancyBrand<T extends EntityTenancy> {
8
- readonly [TENANCY]: T;
9
- }