@cosmicdrift/kumiko-types 0.413.1 → 0.414.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.
@@ -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 ANONYMOUS_ACCESS_ROLE = "anonymous";
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(ANONYMOUS_ACCESS_ROLE))
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 (anySignedIn === true && Array.isArray(rule.roles) && rule.roles.includes(ANONYMOUS_ACCESS_ROLE));
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` and `allowPerRequest`. The write side does not check the value against
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;
@@ -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;
@@ -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; see WriteHandlerDef.excludedFields. */
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) => Promise<WriteResult<DeleteContext>>;
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
@@ -37,6 +37,7 @@ export type SecretKeyDefinition = {
37
37
  };
38
38
  readonly scope: "tenant";
39
39
  readonly required?: boolean;
40
+ readonly requiredWhenConfigSet?: string;
40
41
  readonly writeRoles?: readonly string[];
41
42
  readonly valueSchema?: ZodType<string>;
42
43
  };
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 { DeliveryAttemptLogFactory, 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,8 @@ 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 _deliveryAttemptLogFactory?: DeliveryAttemptLogFactory;
48
+ readonly _logOwnDeliveryAttempt?: DeliveryAttemptLogFn;
47
49
  readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
48
50
  readonly extensions: Readonly<Record<string, unknown>>;
49
51
  };
@@ -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;
@@ -162,15 +163,7 @@ export type NotifyOptions = {
162
163
  readonly immediate?: boolean;
163
164
  readonly locale?: string;
164
165
  };
165
- export type NotifyDeliveryStatus = "queued" | "sent" | "failed" | "skipped";
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;
166
+ 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
167
  export type NotifyDelivery = {
175
168
  readonly channel: string;
176
169
  readonly recipientId: string | null;
@@ -244,6 +237,8 @@ type SharedContextFields = {
244
237
  readonly _notifyFactory?: NotifyFactory;
245
238
  readonly _notifyInTransactionFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
246
239
  readonly _notifyAfterCommitFactory?: (user: SessionUser, tenantId: TenantId) => NotifyFn;
240
+ readonly _deliveryAttemptLogFactory?: DeliveryAttemptLogFactory;
241
+ readonly _logOwnDeliveryAttempt?: DeliveryAttemptLogFn;
247
242
  readonly _escapeHatchAuditSink?: EscapeHatchAuditSink;
248
243
  readonly secrets?: import("./secrets-types.ts").SecretsContext;
249
244
  readonly masterKeyProvider?: import("./secrets-types.ts").MasterKeyProvider;
@@ -268,9 +263,10 @@ export type AppContext = SharedContextFields & {
268
263
  readonly id: string;
269
264
  readonly tenantId: TenantId;
270
265
  } | null;
271
- /** For job-handler calls triggered by an event
272
- * (`r.job` with `trigger: { on: ... }`): the qualified name of the event
273
- * that fired the trigger, e.g. `inbound-mail-foundation:event:inbound-message-received`.
266
+ /** For job-handler calls triggered by an `on` entry
267
+ * (`r.job` with `trigger: { on: ... }`): the qualified name the entry matched,
268
+ * either a write/query-handler QN (e.g. `multi:write:order:open`) or an
269
+ * `r.defineEvent` QN (e.g. `inbound-mail-foundation:event:inbound-message-received`).
274
270
  * For multi-trigger jobs (`on: [...]`), this is the only way for the
275
271
  * handler to know which trigger fired. Cron and manual jobs leave the
276
272
  * field undefined. */
@@ -348,7 +344,6 @@ export type JobContext = {
348
344
  readonly dispatch: JobDispatch;
349
345
  readonly triggerName?: string;
350
346
  readonly triggerEvent?: JobTriggerEvent;
351
- readonly _tenantId?: TenantId;
352
347
  readonly config?: ConfigAccessor;
353
348
  readonly notify?: NotifyFn;
354
349
  readonly services: ContextServices;
@@ -546,7 +541,9 @@ export type BeforeTransactionResult<TPrepared = unknown> = {
546
541
  readonly release?: ReservationRelease;
547
542
  } | WriteFailure;
548
543
  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. */
544
+ /** `perCommand`: the handler runs without a transaction of its own. Use it for long external calls
545
+ * (minutes) that also need writes: no transaction or connection is held while the call runs, and
546
+ * `ctx.db.outsideTransaction` stays available for metering/provenance bookings. */
550
547
  export type WriteHandlerTransactionMode = "perCommand";
551
548
  /** `skip`: run as a nested write without the `beforeTransaction` phase. */
552
549
  export type BeforeTransactionWhenNested = "skip";
@@ -558,6 +555,9 @@ export type WriteHandlerDef = {
558
555
  readonly name: string;
559
556
  readonly schema: ZodType;
560
557
  readonly handler: WriteHandlerFn;
558
+ /** Set only by defineEntityWriteHandler, which wires the parent-ref visibility gate.
559
+ * The boot-validator requires it on a `parentRef` entity's update/delete/restore handler. */
560
+ readonly [ENTITY_CONVENTION_WRITE_BRAND]?: true;
561
561
  readonly access: AccessRule;
562
562
  readonly description?: string;
563
563
  readonly agent?: AgentHandlerHints;
@@ -600,6 +600,7 @@ export type WriteHandlerDef = {
600
600
  readonly perform?: import("./step.ts").PipelineDef;
601
601
  };
602
602
  export declare const ENTITY_CONVENTION_QUERY_BRAND = "__kumikoEntityConventionQuery";
603
+ export declare const ENTITY_CONVENTION_WRITE_BRAND = "__kumikoEntityConventionWrite";
603
604
  export type QueryHandlerDef = {
604
605
  readonly name: string;
605
606
  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
- // Closed vocabulary for delivery failures and skips. Only these values (plus
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";
@@ -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;
@@ -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,3 @@
1
+ export declare const MINOR_UNIT_SCALE = 100;
2
+ export declare function toMajorUnits(minorAmount: number): number;
3
+ export declare function toMinorUnits(amount: number): number;
@@ -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
+ }
@@ -16,8 +16,34 @@ 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
+ /** Shown instead of the generic "no matches" text when this filter yields an empty list and no search term is set; both are i18n keys. */
35
+ readonly emptyState?: {
36
+ readonly title: string;
37
+ readonly description?: string;
38
+ };
39
+ /** Set by the framework from the entity filter's role rule; the chip is offered only to users holding one of them. */
40
+ readonly roles?: readonly string[];
41
+ };
42
+ /** How the named filters appear in the toolbar: toggle chips (default) or one single-choice select. */
43
+ export type NamedFilterFacetOptions = {
44
+ readonly display?: "chips" | "select";
45
+ /** Ask the list query for each filter's hit count and show it next to the label. */
46
+ readonly showCounts?: boolean;
47
+ /** i18n key for the select's label, e.g. "To do". */
48
+ readonly label?: string;
23
49
  };
@@ -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 = {