@cosmicdrift/kumiko-types 0.413.1 → 0.415.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/dist/access-predicates.d.ts +1 -0
- package/dist/access-predicates.js +3 -3
- package/dist/config.d.ts +4 -2
- package/dist/config.js +10 -1
- package/dist/db-connection.d.ts +0 -3
- package/dist/define-handler.d.ts +1 -1
- package/dist/delivery-attempt-types.d.ts +22 -0
- package/dist/delivery-attempt-types.js +31 -0
- package/dist/entity-table-meta-types.d.ts +2 -0
- package/dist/event-store-executor-types.d.ts +4 -1
- package/dist/feature.d.ts +1 -0
- package/dist/fields.d.ts +2 -0
- package/dist/handler-context-facets.d.ts +5 -2
- package/dist/handler-context-services.d.ts +2 -1
- package/dist/handlers.d.ts +17 -15
- package/dist/handlers.js +4 -32
- package/dist/hooks.d.ts +1 -0
- package/dist/identifiers.d.ts +7 -0
- package/dist/identifiers.js +20 -0
- package/dist/minor-units.d.ts +3 -0
- package/dist/minor-units.js +11 -0
- package/dist/multi-stream-apply-context-types.d.ts +3 -1
- package/dist/named-list-filters.d.ts +39 -0
- package/dist/nav-icon.js +1 -1
- package/dist/observability/provider.d.ts +0 -2
- package/dist/projection.d.ts +2 -1
- package/dist/screen-dashboard.d.ts +139 -22
- package/dist/screen-entity-edit.d.ts +2 -0
- package/dist/screen-entity-list.d.ts +83 -19
- package/dist/screen-list-table.d.ts +49 -3
- package/dist/screen-list-table.js +35 -1
- package/dist/screen-projection.d.ts +58 -4
- package/dist/screen-record-guidance.d.ts +96 -0
- package/dist/screen-record-guidance.js +1 -0
- package/dist/screen.d.ts +9 -5
- package/dist/screen.js +1 -1
- package/package.json +9 -1
- package/src/changes.json +6 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AccessRule } from "./handlers.ts";
|
|
2
2
|
export declare function isOpenToAllGranted(rule: AccessRule): boolean;
|
|
3
|
+
export declare const ANONYMOUS_ROLE: "anonymous";
|
|
3
4
|
export declare function isUiAccessGranted(access: AccessRule | undefined, userRoles: readonly string[] | undefined): boolean;
|
|
4
5
|
export declare function isAnySignedInGranted(rule: AccessRule): boolean;
|
|
@@ -11,7 +11,7 @@ export function isOpenToAllGranted(rule) {
|
|
|
11
11
|
return false;
|
|
12
12
|
return openToAll.reason.trim().length > 0;
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
export const ANONYMOUS_ROLE = "anonymous";
|
|
15
15
|
// UI-visibility predicate — DEFAULT-VISIBLE, the opposite stance from
|
|
16
16
|
// `hasAccess` and the default-deny doc on AccessRule (handlers.ts): an unset `access`
|
|
17
17
|
// means every signed-in user may see the screen/nav/workspace, not nobody.
|
|
@@ -28,7 +28,7 @@ export function isUiAccessGranted(access, userRoles) {
|
|
|
28
28
|
return false;
|
|
29
29
|
if (!("roles" in access))
|
|
30
30
|
return false;
|
|
31
|
-
if (isAnySignedInGranted(access) && !userRoles.includes(
|
|
31
|
+
if (isAnySignedInGranted(access) && !userRoles.includes(ANONYMOUS_ROLE))
|
|
32
32
|
return true;
|
|
33
33
|
return access.roles.some((role) => userRoles.includes(role));
|
|
34
34
|
}
|
|
@@ -38,5 +38,5 @@ export function isAnySignedInGranted(rule) {
|
|
|
38
38
|
if (!("roles" in rule) || !("anySignedIn" in rule))
|
|
39
39
|
return false;
|
|
40
40
|
const anySignedIn = rule.anySignedIn;
|
|
41
|
-
return
|
|
41
|
+
return anySignedIn === true && Array.isArray(rule.roles) && rule.roles.includes(ANONYMOUS_ROLE);
|
|
42
42
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export type ConfigKeyDefinition<T extends ConfigKeyType = ConfigKeyType> = {
|
|
|
56
56
|
readonly options?: readonly string[];
|
|
57
57
|
/** Only on `type: "select"`: loads the options from this query (QN), contract
|
|
58
58
|
* `{ rows: { value, label, description?, group? }[] }`, labels shown verbatim. Mutually exclusive with
|
|
59
|
-
* `options
|
|
59
|
+
* `options`. The write side does not check the value against
|
|
60
60
|
* the query result; the consuming feature validates it where it uses it. */
|
|
61
61
|
readonly optionsQuery?: string;
|
|
62
62
|
/** Payload sent with every `optionsQuery` call. Requires `optionsQuery`. `{ field }`
|
|
@@ -70,7 +70,6 @@ export type ConfigKeyDefinition<T extends ConfigKeyType = ConfigKeyType> = {
|
|
|
70
70
|
};
|
|
71
71
|
readonly computed?: ConfigComputedFn<T>;
|
|
72
72
|
readonly validate?: ConfigValidateFn<T>;
|
|
73
|
-
readonly allowPerRequest?: boolean;
|
|
74
73
|
readonly required?: boolean;
|
|
75
74
|
readonly env?: string;
|
|
76
75
|
readonly inheritedToTenant?: boolean;
|
|
@@ -165,6 +164,8 @@ export type MaxPerTenantOptions = {
|
|
|
165
164
|
readonly onLimit: "skip" | "defer";
|
|
166
165
|
readonly deferMs?: number | undefined;
|
|
167
166
|
};
|
|
167
|
+
export declare const IRREVERSIBLE_WITHOUT_ENTRY_HANDLER_CASES: readonly ["rebuildable-derived-data", "high-risk-or-cron-pipeline", "per-dispatch-record-key"];
|
|
168
|
+
export type IrreversibleWithoutEntryHandler = (typeof IRREVERSIBLE_WITHOUT_ENTRY_HANDLER_CASES)[number];
|
|
168
169
|
export type JobDefinition = {
|
|
169
170
|
readonly name: string;
|
|
170
171
|
readonly handler: JobHandlerFn;
|
|
@@ -190,6 +191,7 @@ export type JobDefinition = {
|
|
|
190
191
|
readonly tenantVisibleRun?: {
|
|
191
192
|
readonly subjectFields?: readonly string[] | undefined;
|
|
192
193
|
} | undefined;
|
|
194
|
+
readonly irreversibleWithoutEntryHandler?: IrreversibleWithoutEntryHandler | undefined;
|
|
193
195
|
};
|
|
194
196
|
export type NotificationRecipientFn = (result: import("./hooks.ts").SaveContext) => string | readonly string[] | {
|
|
195
197
|
readonly tenant: string;
|
package/dist/config.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// The only pipelines allowed to call an irreversible primitive (forget, hard delete, kms.eraseKey,
|
|
2
|
+
// readAsInstruction write) without a directly dispatched entry handler. A new case is a framework change.
|
|
3
|
+
// rebuildable-derived-data deletes derived data that is re-created from a reversible source
|
|
4
|
+
// high-risk-or-cron-pipeline runs behind a risk "high" handler or as a cron scan of expired data
|
|
5
|
+
// per-dispatch-record-key the step-dispatcher erasing its per-dispatch record key
|
|
6
|
+
export const IRREVERSIBLE_WITHOUT_ENTRY_HANDLER_CASES = [
|
|
7
|
+
"rebuildable-derived-data",
|
|
8
|
+
"high-risk-or-cron-pipeline",
|
|
9
|
+
"per-dispatch-record-key",
|
|
10
|
+
];
|
package/dist/db-connection.d.ts
CHANGED
|
@@ -2,9 +2,6 @@ import type { SQL, TransactionSQL } from "bun";
|
|
|
2
2
|
export type RawDbClient = {
|
|
3
3
|
unsafe: (sql: string, params?: readonly unknown[]) => PromiseLike<readonly unknown[]>;
|
|
4
4
|
begin: <T>(fn: (tx: DbTx) => Promise<T>) => Promise<T>;
|
|
5
|
-
end?: (options?: {
|
|
6
|
-
timeout?: number;
|
|
7
|
-
}) => Promise<void>;
|
|
8
5
|
};
|
|
9
6
|
export type DbConnection = SQL | RawDbClient;
|
|
10
7
|
export type DbTx = TransactionSQL | RawDbClient;
|
package/dist/define-handler.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type WriteHandlerDefinition<TName extends string = string, TSchema extend
|
|
|
9
9
|
readonly access: AccessRule;
|
|
10
10
|
readonly description?: string;
|
|
11
11
|
readonly agent?: AgentHandlerHints;
|
|
12
|
-
/** Set by the entity convention create/update handlers;
|
|
12
|
+
/** Set by the entity convention create/update handlers; a hand-written handler may declare it for its form, its own schema still decides what it accepts. See WriteHandlerDef.excludedFields. */
|
|
13
13
|
readonly excludedFields?: readonly string[];
|
|
14
14
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
15
15
|
readonly rateLimit?: RateLimitDeclaration;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DbConnection } from "./db-connection.ts";
|
|
2
|
+
import type { NotifyPriority, SessionUser } from "./handlers.ts";
|
|
3
|
+
import type { TenantId } from "./identifiers.ts";
|
|
4
|
+
export type NotifyDeliveryStatus = "queued" | "sent" | "failed" | "skipped";
|
|
5
|
+
export declare const DELIVERY_FAILURE_CODES: readonly ["timeout", "network_error", "redirect_blocked", "host_not_allowed", "missing_credentials", "invalid_address", "unexpected_response", "render_failed", "send_failed", "channel_error"];
|
|
6
|
+
export declare const DELIVERY_SKIP_REASONS: readonly ["channel_disabled", "preference_disabled", "rate_limited", "no_address", "unsubscribed", "duplicate_idempotency_key"];
|
|
7
|
+
export type DeliveryFailureCode = (typeof DELIVERY_FAILURE_CODES)[number];
|
|
8
|
+
export type DeliverySkipReason = (typeof DELIVERY_SKIP_REASONS)[number];
|
|
9
|
+
export type DeliveryErrorCode = DeliveryFailureCode | DeliverySkipReason | `http_${number}`;
|
|
10
|
+
export type ChatSendFailureCode = `http_${number}` | Exclude<DeliveryFailureCode, "render_failed" | "send_failed" | "channel_error">;
|
|
11
|
+
export declare const HTTP_ERROR_CODE_PATTERN: RegExp;
|
|
12
|
+
export declare function isDeliveryErrorCode(value: unknown): value is DeliveryErrorCode;
|
|
13
|
+
export type DeliveryAttemptLogEntry = {
|
|
14
|
+
readonly notificationType: string;
|
|
15
|
+
readonly channel: string;
|
|
16
|
+
readonly recipientAddress: string | null;
|
|
17
|
+
readonly status: NotifyDeliveryStatus;
|
|
18
|
+
readonly error: DeliveryErrorCode | null;
|
|
19
|
+
readonly priority: NotifyPriority;
|
|
20
|
+
};
|
|
21
|
+
export type DeliveryAttemptLogFn = (entry: DeliveryAttemptLogEntry) => Promise<void>;
|
|
22
|
+
export type DeliveryAttemptLogFactory = (user: SessionUser, tenantId: TenantId, deliveryDb?: DbConnection) => DeliveryAttemptLogFn;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Closed vocabulary for delivery failures and skips. Only these values (plus
|
|
2
|
+
// `http_<status>`) are ever stored or returned as a delivery error, never a raw
|
|
3
|
+
// error message: provider errors can carry URLs, tokens and addresses.
|
|
4
|
+
export const DELIVERY_FAILURE_CODES = [
|
|
5
|
+
"timeout",
|
|
6
|
+
"network_error",
|
|
7
|
+
"redirect_blocked",
|
|
8
|
+
"host_not_allowed",
|
|
9
|
+
"missing_credentials",
|
|
10
|
+
"invalid_address",
|
|
11
|
+
"unexpected_response",
|
|
12
|
+
"render_failed",
|
|
13
|
+
"send_failed",
|
|
14
|
+
"channel_error",
|
|
15
|
+
];
|
|
16
|
+
export const DELIVERY_SKIP_REASONS = [
|
|
17
|
+
"channel_disabled",
|
|
18
|
+
"preference_disabled",
|
|
19
|
+
"rate_limited",
|
|
20
|
+
"no_address",
|
|
21
|
+
"unsubscribed",
|
|
22
|
+
"duplicate_idempotency_key",
|
|
23
|
+
];
|
|
24
|
+
export const HTTP_ERROR_CODE_PATTERN = /^http_(\d{3})$/;
|
|
25
|
+
export function isDeliveryErrorCode(value) {
|
|
26
|
+
if (typeof value !== "string")
|
|
27
|
+
return false;
|
|
28
|
+
return (HTTP_ERROR_CODE_PATTERN.test(value) ||
|
|
29
|
+
DELIVERY_FAILURE_CODES.some((code) => code === value) ||
|
|
30
|
+
DELIVERY_SKIP_REASONS.some((reason) => reason === value));
|
|
31
|
+
}
|
|
@@ -30,6 +30,7 @@ export type EntityTableMeta = {
|
|
|
30
30
|
readonly source: "managed" | "unmanaged";
|
|
31
31
|
readonly piiSubjectFields?: readonly string[];
|
|
32
32
|
readonly tenancy?: "global";
|
|
33
|
+
readonly systemRowsReadable?: true;
|
|
33
34
|
};
|
|
34
35
|
export type BuildEntityTableMetaOptions = {
|
|
35
36
|
readonly featureName?: string;
|
|
@@ -42,4 +43,5 @@ export type UnmanagedTableInput = {
|
|
|
42
43
|
readonly indexes?: readonly IndexMeta[];
|
|
43
44
|
readonly compositePrimaryKey?: CompositePrimaryKeyMeta;
|
|
44
45
|
readonly tenancy?: EntityTenancy;
|
|
46
|
+
readonly systemRowsReadable?: true;
|
|
45
47
|
};
|
|
@@ -15,6 +15,7 @@ export type ParentVisibilityOption = {
|
|
|
15
15
|
}>;
|
|
16
16
|
readonly canReadEntity?: (entityName: string) => boolean;
|
|
17
17
|
readonly resolveTable?: (entityName: string) => SchemaTable;
|
|
18
|
+
readonly globalHostIds?: ReadonlyMap<string, readonly string[]>;
|
|
18
19
|
};
|
|
19
20
|
export type ParentVisibilityGateOption = {
|
|
20
21
|
readonly parentVisibility?: ParentVisibilityOption;
|
|
@@ -91,7 +92,9 @@ export type EventStoreExecutor<Row extends Record<string, unknown> = Record<stri
|
|
|
91
92
|
}) => Promise<WriteResult<SaveContext>>;
|
|
92
93
|
delete: (payload: {
|
|
93
94
|
id: EntityId;
|
|
94
|
-
}, user: SessionUser, db: TenantDb, options?: StreamTenantOption & LockGuardOption & ParentVisibilityGateOption
|
|
95
|
+
}, user: SessionUser, db: TenantDb, options?: StreamTenantOption & LockGuardOption & ParentVisibilityGateOption & {
|
|
96
|
+
expect?: Readonly<Record<string, string | number | boolean | null>>;
|
|
97
|
+
}) => Promise<WriteResult<DeleteContext>>;
|
|
95
98
|
forget: (payload: {
|
|
96
99
|
id: EntityId;
|
|
97
100
|
}, user: SessionUser, db: TenantDb, options?: StreamTenantOption & ParentVisibilityGateOption) => Promise<WriteResult<DeleteContext>>;
|
package/dist/feature.d.ts
CHANGED
package/dist/fields.d.ts
CHANGED
|
@@ -728,6 +728,8 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap, T extends EntityTe
|
|
|
728
728
|
* only for genuinely tenant-independent aggregates like `user`. The first
|
|
729
729
|
* event (create) is what's routed; updates resolve the stream tenant upstream. */
|
|
730
730
|
readonly systemStream?: boolean;
|
|
731
|
+
/** Tenant sessions may read this entity's SYSTEM_TENANT_ID rows (shared templates, config defaults, catalogs). Without it the table's row level security hides them. Never set it on credential, session, secret or PII entities. */
|
|
732
|
+
readonly systemRowsReadable?: boolean;
|
|
731
733
|
/** Lets token scopes release `<entity>.created/.updated/.deleted` as webhook events. Off by default: bundle entities can hold secrets. */
|
|
732
734
|
readonly webhookEvents?: boolean;
|
|
733
735
|
/** Schema version stamped on every lifecycle event (default 1, integer 1..999). Bump it together with a step in `eventMigrations`. */
|
|
@@ -40,9 +40,11 @@ export type HandlerEvents<TMap extends object = KumikoEventTypeMap> = {
|
|
|
40
40
|
readonly load: (aggregateId: string, options?: {
|
|
41
41
|
readonly asOf?: Temporal.Instant;
|
|
42
42
|
readonly atVersion?: never;
|
|
43
|
+
readonly includeArchived?: boolean;
|
|
43
44
|
} | {
|
|
44
45
|
readonly atVersion?: number;
|
|
45
46
|
readonly asOf?: never;
|
|
47
|
+
readonly includeArchived?: boolean;
|
|
46
48
|
}) => Promise<readonly import("./event-store-types.ts").StoredEvent[]>;
|
|
47
49
|
readonly archive: (aggregateId: string, args: {
|
|
48
50
|
readonly aggregateType: string;
|
|
@@ -85,7 +87,7 @@ export type HandlerObservability = {
|
|
|
85
87
|
readonly metricsFor: (featureName: string) => MetricsHandle;
|
|
86
88
|
readonly meter?: Meter;
|
|
87
89
|
};
|
|
88
|
-
export type JobEvents = Pick<HandlerEvents, "append">;
|
|
90
|
+
export type JobEvents = Pick<HandlerEvents, "append" | "load">;
|
|
89
91
|
export type JobDispatch = Pick<HandlerDispatch, "write" | "writeAs" | "queryAs" | "queryAsMember">;
|
|
90
92
|
export type JobObservability = Pick<HandlerObservability, "log" | "metricsFor" | "meter">;
|
|
91
93
|
type PhaseWithoutTransaction = "events" | "notify" | "dispatch" | "lifecycle";
|
|
@@ -97,7 +99,8 @@ export type ReservationContext<TMap extends object = KumikoEventTypeMap> = Omit<
|
|
|
97
99
|
readonly dispatch: Omit<HandlerDispatch, "write" | "writeAs">;
|
|
98
100
|
readonly lifecycle: Omit<HandlerLifecycle, "afterCommit" | "afterRollback">;
|
|
99
101
|
};
|
|
100
|
-
/** ctx of `beforeTransaction`: external I/O and reads only, so the outside-transaction handles are gone too.
|
|
102
|
+
/** ctx of `beforeTransaction`: external I/O and reads only, so the outside-transaction handles are gone too.
|
|
103
|
+
* A long external call that must also write (metering, provenance) belongs in `transaction: "perCommand"`. */
|
|
101
104
|
export type BeforeTransactionContext<TMap extends object = KumikoEventTypeMap> = Omit<ReservationContext<TMap>, "db" | "systemDb"> & {
|
|
102
105
|
readonly db: TenantDb & {
|
|
103
106
|
readonly outsideTransaction: undefined;
|
|
@@ -6,7 +6,7 @@ import type { EntityCache } from "./entity-cache.ts";
|
|
|
6
6
|
import type { EnvelopeCipher } from "./envelope-cipher-types.ts";
|
|
7
7
|
import type { FileContext } from "./file-handle-types.ts";
|
|
8
8
|
import type { FileProviderResolver } from "./file-provider-resolver-types.ts";
|
|
9
|
-
import type { EscapeHatchAuditSink, JobRunnerRef, NotifyFactory, NotifyFn, SessionUser, TenantWriteTarget } from "./handlers.ts";
|
|
9
|
+
import type { DeliveryAttemptLogFn, EscapeHatchAuditSink, JobRunnerRef, NotifyFactory, NotifyFn, SessionUser, TenantWriteTarget } from "./handlers.ts";
|
|
10
10
|
import type { TenantId } from "./identifiers.ts";
|
|
11
11
|
import type { KmsAdapter } from "./kms-adapter-types.ts";
|
|
12
12
|
import type { RateLimitResolver } from "./rate-limit-types.ts";
|
|
@@ -44,6 +44,7 @@ export type ContextServices = {
|
|
|
44
44
|
readonly _notifyFactory?: NotifyFactory;
|
|
45
45
|
readonly _notifyInTransactionFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
|
|
46
46
|
readonly _notifyAfterCommitFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
|
|
47
|
+
readonly _logOwnDeliveryAttempt?: DeliveryAttemptLogFn;
|
|
47
48
|
readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
48
49
|
readonly extensions: Readonly<Record<string, unknown>>;
|
|
49
50
|
};
|
package/dist/handlers.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { ZodType } from "zod";
|
|
|
4
4
|
import type { CacheSyncBus } from "./cache-sync-types.ts";
|
|
5
5
|
import type { ConfigAccessor, ConfigAccessorFactory, ConfigResolver } from "./config.ts";
|
|
6
6
|
import type { DbConnection, DbTx } from "./db-connection.ts";
|
|
7
|
+
import type { DeliveryAttemptLogFactory, DeliveryAttemptLogFn, DeliveryErrorCode, NotifyDeliveryStatus } from "./delivery-attempt-types.ts";
|
|
7
8
|
import type { DerivativesContext } from "./derivatives-types.ts";
|
|
8
9
|
import type { EntityCache } from "./entity-cache.ts";
|
|
9
10
|
import type { EventMetadata, WriteOrigin } from "./event-store-types.ts";
|
|
@@ -49,7 +50,7 @@ export declare function crossTenantReadsGrantFor(declaration: EscapeHatchDeclara
|
|
|
49
50
|
export declare function escapeHatchGrantsProblem(declaration: {
|
|
50
51
|
readonly grants?: unknown;
|
|
51
52
|
}): string | undefined;
|
|
52
|
-
export { isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.ts";
|
|
53
|
+
export { ANONYMOUS_ROLE, isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.ts";
|
|
53
54
|
export type SessionUserOrigin = "member-resolution";
|
|
54
55
|
export type SessionUser = {
|
|
55
56
|
readonly id: string;
|
|
@@ -142,6 +143,7 @@ export type JobRunnerRef = {
|
|
|
142
143
|
readonly priority?: number;
|
|
143
144
|
readonly jobId?: string;
|
|
144
145
|
}): Promise<unknown>;
|
|
146
|
+
isJobPending?(jobName: string, jobId: string): Promise<boolean>;
|
|
145
147
|
};
|
|
146
148
|
export type DispatchWriteRef = {
|
|
147
149
|
readonly write: (user: SessionUser, qn: string, payload: unknown, inheritedOrigin?: WriteOrigin) => Promise<WriteResult>;
|
|
@@ -162,15 +164,7 @@ export type NotifyOptions = {
|
|
|
162
164
|
readonly immediate?: boolean;
|
|
163
165
|
readonly locale?: string;
|
|
164
166
|
};
|
|
165
|
-
export type
|
|
166
|
-
export declare const DELIVERY_FAILURE_CODES: readonly ["timeout", "network_error", "redirect_blocked", "host_not_allowed", "missing_credentials", "invalid_address", "unexpected_response", "render_failed", "send_failed", "channel_error"];
|
|
167
|
-
export declare const DELIVERY_SKIP_REASONS: readonly ["channel_disabled", "preference_disabled", "rate_limited", "no_address", "unsubscribed", "duplicate_idempotency_key"];
|
|
168
|
-
export type DeliveryFailureCode = (typeof DELIVERY_FAILURE_CODES)[number];
|
|
169
|
-
export type DeliverySkipReason = (typeof DELIVERY_SKIP_REASONS)[number];
|
|
170
|
-
export type DeliveryErrorCode = DeliveryFailureCode | DeliverySkipReason | `http_${number}`;
|
|
171
|
-
export type ChatSendFailureCode = `http_${number}` | Exclude<DeliveryFailureCode, "render_failed" | "send_failed" | "channel_error">;
|
|
172
|
-
export declare const HTTP_ERROR_CODE_PATTERN: RegExp;
|
|
173
|
-
export declare function isDeliveryErrorCode(value: unknown): value is DeliveryErrorCode;
|
|
167
|
+
export { type ChatSendFailureCode, DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, type DeliveryAttemptLogEntry, type DeliveryAttemptLogFactory, type DeliveryAttemptLogFn, type DeliveryErrorCode, type DeliveryFailureCode, type DeliverySkipReason, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, type NotifyDeliveryStatus, } from "./delivery-attempt-types.ts";
|
|
174
168
|
export type NotifyDelivery = {
|
|
175
169
|
readonly channel: string;
|
|
176
170
|
readonly recipientId: string | null;
|
|
@@ -244,6 +238,8 @@ type SharedContextFields = {
|
|
|
244
238
|
readonly _notifyFactory?: NotifyFactory;
|
|
245
239
|
readonly _notifyInTransactionFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
|
|
246
240
|
readonly _notifyAfterCommitFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
|
|
241
|
+
readonly _deliveryAttemptLogFactory?: DeliveryAttemptLogFactory;
|
|
242
|
+
readonly _logOwnDeliveryAttempt?: DeliveryAttemptLogFn;
|
|
247
243
|
readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
248
244
|
readonly secrets?: import("./secrets-types.ts").SecretsContext;
|
|
249
245
|
readonly masterKeyProvider?: import("./secrets-types.ts").MasterKeyProvider;
|
|
@@ -268,9 +264,10 @@ export type AppContext = SharedContextFields & {
|
|
|
268
264
|
readonly id: string;
|
|
269
265
|
readonly tenantId: TenantId;
|
|
270
266
|
} | null;
|
|
271
|
-
/** For job-handler calls triggered by an
|
|
272
|
-
* (`r.job` with `trigger: { on: ... }`): the qualified name
|
|
273
|
-
*
|
|
267
|
+
/** For job-handler calls triggered by an `on` entry
|
|
268
|
+
* (`r.job` with `trigger: { on: ... }`): the qualified name the entry matched,
|
|
269
|
+
* either a write/query-handler QN (e.g. `multi:write:order:open`) or an
|
|
270
|
+
* `r.defineEvent` QN (e.g. `inbound-mail-foundation:event:inbound-message-received`).
|
|
274
271
|
* For multi-trigger jobs (`on: [...]`), this is the only way for the
|
|
275
272
|
* handler to know which trigger fired. Cron and manual jobs leave the
|
|
276
273
|
* field undefined. */
|
|
@@ -348,7 +345,6 @@ export type JobContext = {
|
|
|
348
345
|
readonly dispatch: JobDispatch;
|
|
349
346
|
readonly triggerName?: string;
|
|
350
347
|
readonly triggerEvent?: JobTriggerEvent;
|
|
351
|
-
readonly _tenantId?: TenantId;
|
|
352
348
|
readonly config?: ConfigAccessor;
|
|
353
349
|
readonly notify?: NotifyFn;
|
|
354
350
|
readonly services: ContextServices;
|
|
@@ -546,7 +542,9 @@ export type BeforeTransactionResult<TPrepared = unknown> = {
|
|
|
546
542
|
readonly release?: ReservationRelease;
|
|
547
543
|
} | WriteFailure;
|
|
548
544
|
export type BeforeTransactionPhase<TPayload = unknown, TPrepared = unknown> = (event: WriteEvent<TPayload>, ctx: BeforeTransactionContext) => Promise<BeforeTransactionResult<TPrepared>>;
|
|
549
|
-
/** `perCommand`: the handler runs without a transaction of its own.
|
|
545
|
+
/** `perCommand`: the handler runs without a transaction of its own. Use it for long external calls
|
|
546
|
+
* (minutes) that also need writes: no transaction or connection is held while the call runs, and
|
|
547
|
+
* `ctx.db.outsideTransaction` stays available for metering/provenance bookings. */
|
|
550
548
|
export type WriteHandlerTransactionMode = "perCommand";
|
|
551
549
|
/** `skip`: run as a nested write without the `beforeTransaction` phase. */
|
|
552
550
|
export type BeforeTransactionWhenNested = "skip";
|
|
@@ -558,6 +556,9 @@ export type WriteHandlerDef = {
|
|
|
558
556
|
readonly name: string;
|
|
559
557
|
readonly schema: ZodType;
|
|
560
558
|
readonly handler: WriteHandlerFn;
|
|
559
|
+
/** Set only by defineEntityWriteHandler, which wires the parent-ref visibility gate.
|
|
560
|
+
* The boot-validator requires it on a `parentRef` entity's update/delete/restore handler. */
|
|
561
|
+
readonly [ENTITY_CONVENTION_WRITE_BRAND]?: true;
|
|
561
562
|
readonly access: AccessRule;
|
|
562
563
|
readonly description?: string;
|
|
563
564
|
readonly agent?: AgentHandlerHints;
|
|
@@ -600,6 +601,7 @@ export type WriteHandlerDef = {
|
|
|
600
601
|
readonly perform?: import("./step.ts").PipelineDef;
|
|
601
602
|
};
|
|
602
603
|
export declare const ENTITY_CONVENTION_QUERY_BRAND = "__kumikoEntityConventionQuery";
|
|
604
|
+
export declare const ENTITY_CONVENTION_WRITE_BRAND = "__kumikoEntityConventionWrite";
|
|
603
605
|
export type QueryHandlerDef = {
|
|
604
606
|
readonly name: string;
|
|
605
607
|
readonly schema: ZodType;
|
package/dist/handlers.js
CHANGED
|
@@ -39,38 +39,8 @@ export function escapeHatchGrantsProblem(declaration) {
|
|
|
39
39
|
}
|
|
40
40
|
return undefined;
|
|
41
41
|
}
|
|
42
|
-
export { isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.js";
|
|
43
|
-
|
|
44
|
-
// `http_<status>`) are ever stored or returned as a delivery error, never a raw
|
|
45
|
-
// error message: provider errors can carry URLs, tokens and addresses.
|
|
46
|
-
export const DELIVERY_FAILURE_CODES = [
|
|
47
|
-
"timeout",
|
|
48
|
-
"network_error",
|
|
49
|
-
"redirect_blocked",
|
|
50
|
-
"host_not_allowed",
|
|
51
|
-
"missing_credentials",
|
|
52
|
-
"invalid_address",
|
|
53
|
-
"unexpected_response",
|
|
54
|
-
"render_failed",
|
|
55
|
-
"send_failed",
|
|
56
|
-
"channel_error",
|
|
57
|
-
];
|
|
58
|
-
export const DELIVERY_SKIP_REASONS = [
|
|
59
|
-
"channel_disabled",
|
|
60
|
-
"preference_disabled",
|
|
61
|
-
"rate_limited",
|
|
62
|
-
"no_address",
|
|
63
|
-
"unsubscribed",
|
|
64
|
-
"duplicate_idempotency_key",
|
|
65
|
-
];
|
|
66
|
-
export const HTTP_ERROR_CODE_PATTERN = /^http_(\d{3})$/;
|
|
67
|
-
export function isDeliveryErrorCode(value) {
|
|
68
|
-
if (typeof value !== "string")
|
|
69
|
-
return false;
|
|
70
|
-
return (HTTP_ERROR_CODE_PATTERN.test(value) ||
|
|
71
|
-
DELIVERY_FAILURE_CODES.some((code) => code === value) ||
|
|
72
|
-
DELIVERY_SKIP_REASONS.some((reason) => reason === value));
|
|
73
|
-
}
|
|
42
|
+
export { ANONYMOUS_ROLE, isAnySignedInGranted, isOpenToAllGranted, isUiAccessGranted, } from "./access-predicates.js";
|
|
43
|
+
export { DELIVERY_FAILURE_CODES, DELIVERY_SKIP_REASONS, HTTP_ERROR_CODE_PATTERN, isDeliveryErrorCode, } from "./delivery-attempt-types.js";
|
|
74
44
|
export function normalizeEventPiiSubject(spec) {
|
|
75
45
|
if ("subjectField" in spec) {
|
|
76
46
|
return { kind: "user", ownerField: spec.subjectField, whenAbsent: undefined };
|
|
@@ -122,3 +92,5 @@ export function isRateLimitDisabled(v) {
|
|
|
122
92
|
// workspace can evaluate a module twice, and two Symbol() calls would not
|
|
123
93
|
// compare equal across those instances.
|
|
124
94
|
export const ENTITY_CONVENTION_QUERY_BRAND = "__kumikoEntityConventionQuery";
|
|
95
|
+
// Same brand for the update/delete/restore handlers built by defineEntityWriteHandler.
|
|
96
|
+
export const ENTITY_CONVENTION_WRITE_BRAND = "__kumikoEntityConventionWrite";
|
package/dist/hooks.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export type SaveContext = {
|
|
|
16
16
|
readonly isNew: boolean;
|
|
17
17
|
readonly entityName?: string | undefined;
|
|
18
18
|
readonly event: StoredEvent;
|
|
19
|
+
readonly claimedFileRefIds?: readonly string[] | undefined;
|
|
19
20
|
};
|
|
20
21
|
export type DeleteContext = {
|
|
21
22
|
readonly kind: "delete";
|
package/dist/identifiers.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
export type TenantId = string;
|
|
2
2
|
export declare const UUID_SHAPE_PATTERN = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
|
|
3
3
|
export declare function isUuid(value: unknown): value is string;
|
|
4
|
+
export type FileHandleParts = {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly claim: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function parseFileHandle(value: unknown): FileHandleParts | null;
|
|
9
|
+
export declare function isFileHandle(value: unknown): value is string;
|
|
10
|
+
export declare function formatFileHandle(parts: FileHandleParts): string;
|
|
4
11
|
export declare function parseTenantId(value: unknown): TenantId | null;
|
|
5
12
|
export declare const SYSTEM_TENANT_ID: TenantId;
|
|
6
13
|
export declare function isSystemTenant(tenantId: TenantId | null | undefined): boolean;
|
package/dist/identifiers.js
CHANGED
|
@@ -20,6 +20,26 @@ const TENANT_ID_REGEX = new RegExp(UUID_SHAPE_PATTERN);
|
|
|
20
20
|
export function isUuid(value) {
|
|
21
21
|
return typeof value === "string" && TENANT_ID_REGEX.test(value);
|
|
22
22
|
}
|
|
23
|
+
// Opaque reference to a pending anonymous upload: `<fileRef uuid>.<claim>`. The claim is 32 random
|
|
24
|
+
// bytes, base64url; the server stores only its hash. Field values accept this form and persist
|
|
25
|
+
// the bare uuid.
|
|
26
|
+
const FILE_HANDLE_CLAIM_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
27
|
+
export function parseFileHandle(value) {
|
|
28
|
+
if (typeof value !== "string")
|
|
29
|
+
return null;
|
|
30
|
+
const separator = value.indexOf(".");
|
|
31
|
+
if (separator === -1)
|
|
32
|
+
return null;
|
|
33
|
+
const id = value.slice(0, separator);
|
|
34
|
+
const claim = value.slice(separator + 1);
|
|
35
|
+
return isUuid(id) && FILE_HANDLE_CLAIM_PATTERN.test(claim) ? { id, claim } : null;
|
|
36
|
+
}
|
|
37
|
+
export function isFileHandle(value) {
|
|
38
|
+
return parseFileHandle(value) !== null;
|
|
39
|
+
}
|
|
40
|
+
export function formatFileHandle(parts) {
|
|
41
|
+
return `${parts.id}.${parts.claim}`;
|
|
42
|
+
}
|
|
23
43
|
// Validates a candidate string against the tenantId format and returns it
|
|
24
44
|
// as a TenantId, or `null` when it doesn't match. Use at every system
|
|
25
45
|
// boundary that admits untrusted input (HTTP headers, cookies, query
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// 2 decimal places (cents) — covers every currently-supported currency
|
|
2
|
+
// (EUR/USD/GBP/...). No ISO-4217 minor-unit table yet (JPY=0, BHD=3) —
|
|
3
|
+
// upgrade path once a currency needing a different scale actually lands.
|
|
4
|
+
// Client-safe on purpose: db/money.ts pulls in engine runtime values.
|
|
5
|
+
export const MINOR_UNIT_SCALE = 100;
|
|
6
|
+
export function toMajorUnits(minorAmount) {
|
|
7
|
+
return minorAmount / MINOR_UNIT_SCALE;
|
|
8
|
+
}
|
|
9
|
+
export function toMinorUnits(amount) {
|
|
10
|
+
return Math.round(amount * MINOR_UNIT_SCALE);
|
|
11
|
+
}
|
|
@@ -8,7 +8,9 @@ import type { AppendEventFn, UnsafeAppendEventFn } from "./handlers.ts";
|
|
|
8
8
|
import type { SecretsContext } from "./secrets-types.ts";
|
|
9
9
|
export type MultiStreamApplyContext<TMap extends object = KumikoEventTypeMap> = {
|
|
10
10
|
readonly appendEvent: AppendEventFn<TMap>;
|
|
11
|
-
readonly unsafeAppendEvent: UnsafeAppendEventFn
|
|
11
|
+
readonly unsafeAppendEvent: (args: Parameters<UnsafeAppendEventFn>[0] & {
|
|
12
|
+
readonly expectedVersion?: number;
|
|
13
|
+
}) => Promise<void>;
|
|
12
14
|
readonly loadAggregate: (aggregateId: string, options?: {
|
|
13
15
|
readonly asOf?: Temporal.Instant;
|
|
14
16
|
}) => Promise<readonly StoredEvent[]>;
|
|
@@ -16,8 +16,47 @@ export type NamedListFilterDef = {
|
|
|
16
16
|
readonly access?: AccessRule;
|
|
17
17
|
readonly filters: (scope: NamedListFilterScope) => readonly NamedListFilterCondition[] | Promise<readonly NamedListFilterCondition[]>;
|
|
18
18
|
};
|
|
19
|
+
export type ListSortDir = "asc" | "desc";
|
|
20
|
+
export type ListSortSpec = {
|
|
21
|
+
readonly field: string;
|
|
22
|
+
readonly dir: ListSortDir;
|
|
23
|
+
};
|
|
24
|
+
/** One entry of a list's "Sorted by" select; `label` is an i18n key. */
|
|
25
|
+
export type ListSortOption = ListSortSpec & {
|
|
26
|
+
readonly label: string;
|
|
27
|
+
};
|
|
19
28
|
/** Screen-side reference to an entity-defined named filter; `label` is an i18n key. */
|
|
20
29
|
export type NamedListFilterRef = {
|
|
21
30
|
readonly id: string;
|
|
22
31
|
readonly label: string;
|
|
32
|
+
/** Order while this filter is active and the URL carries no sort of its own; wins over `defaultSort`. */
|
|
33
|
+
readonly sort?: ListSortSpec;
|
|
34
|
+
/** Select display: draws a divider line below this entry in the dropdown. */
|
|
35
|
+
readonly separatorAfter?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Shown instead of the generic "no matches" text when this filter yields an empty list and no
|
|
38
|
+
* search term is set; `title` and `description` are i18n keys. `paramsQuery` (a registered query
|
|
39
|
+
* QN, called without payload) runs only while the empty state is on screen; its result object
|
|
40
|
+
* becomes the translation params of `description`. Without data (loading, error, no result) the
|
|
41
|
+
* description is not rendered.
|
|
42
|
+
*/
|
|
43
|
+
readonly emptyState?: {
|
|
44
|
+
readonly title: string;
|
|
45
|
+
readonly description?: string;
|
|
46
|
+
readonly paramsQuery?: string;
|
|
47
|
+
};
|
|
48
|
+
/** Set by the framework from the entity filter's access rule; the client offers the chip only where `isUiAccessGranted` allows it. */
|
|
49
|
+
readonly access?: AccessRule;
|
|
50
|
+
};
|
|
51
|
+
/** How the named filters appear in the toolbar: toggle chips (default) or one single-choice select. */
|
|
52
|
+
export type NamedFilterFacetOptions = {
|
|
53
|
+
readonly display?: "chips" | "select";
|
|
54
|
+
/** Ask the list query for each filter's hit count and show it next to the label. */
|
|
55
|
+
readonly showCounts?: boolean;
|
|
56
|
+
/** Select entries with zero hits are left out (the active one and "all" stay). Asks for the counts like `showCounts` does, without showing the number unless `showCounts` is set. */
|
|
57
|
+
readonly hideEmpty?: boolean;
|
|
58
|
+
/** "behind-filter": the select opens behind the filter icon next to the search field instead of standing as a pill. Only below `sm`; otherwise it stays a pill. */
|
|
59
|
+
readonly narrowPlacement?: "behind-filter";
|
|
60
|
+
/** i18n key for the select's label, e.g. "To do". */
|
|
61
|
+
readonly label?: string;
|
|
23
62
|
};
|
package/dist/nav-icon.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// versa.
|
|
11
11
|
//
|
|
12
12
|
// Declared as a runtime tuple (not just a type) so callers that sit outside
|
|
13
|
-
// this repo's
|
|
13
|
+
// this repo's typecheck project graph (e.g. samples, external consumers
|
|
14
14
|
// resolving from a stale published type) still get a runtime-checkable
|
|
15
15
|
// vocabulary — the boot-validator's validateActionHasIcon checks a declared
|
|
16
16
|
// icon against NAV_ICON_KEYS instead of trusting "not undefined".
|
|
@@ -8,8 +8,6 @@ export type SamplingConfig = {
|
|
|
8
8
|
};
|
|
9
9
|
};
|
|
10
10
|
export type SensitiveFilterConfig = {
|
|
11
|
-
readonly redactedHeaders: readonly string[];
|
|
12
|
-
readonly redactedQueryParams: readonly string[];
|
|
13
11
|
readonly redactedAttributeKeyPatterns: readonly RegExp[];
|
|
14
12
|
};
|
|
15
13
|
export type ObservabilityOptions = {
|
package/dist/projection.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunIn } from "./config.ts";
|
|
1
|
+
import type { IrreversibleWithoutEntryHandler, RunIn } from "./config.ts";
|
|
2
2
|
import type { StoredEvent } from "./event-store-types.ts";
|
|
3
3
|
import type { ExecutorManagedRunner } from "./executor-brand.ts";
|
|
4
4
|
import type { EntityDefinition } from "./fields.ts";
|
|
@@ -38,4 +38,5 @@ export type MultiStreamProjectionDefinition = {
|
|
|
38
38
|
readonly runIn?: RunIn;
|
|
39
39
|
readonly delivery?: "shared" | "per-instance";
|
|
40
40
|
readonly startFrom?: "beginning" | "now";
|
|
41
|
+
readonly irreversibleWithoutEntryHandler?: IrreversibleWithoutEntryHandler;
|
|
41
42
|
};
|