@cosmicdrift/kumiko-types 0.259.0 → 0.261.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/package.json +5 -1
- package/src/define-handler.ts +7 -4
- package/src/entity-handlers.ts +8 -3
- package/src/entity-table-meta-types.ts +5 -0
- package/src/feature.ts +7 -5
- package/src/fields.ts +8 -1
- package/src/handlers.ts +31 -6
- package/src/screen.ts +11 -2
- package/src/tenancy-brand.ts +9 -0
- package/src/tenant-db-types.ts +39 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.261.0",
|
|
4
4
|
"description": "Framework-Type-Definitions für Kumiko — FeatureDefinition, BootCheck-Types und die reinen Engine-Types. Erlaubt Downstream-Konsumenten, gegen die Type-Contracts zu bauen, ohne das ganze Framework-Package zu importieren. Enthaelt keine identitaets-sensitiven Runtime-Werte mehr (Error-Klassen leben seit #1629 in kumiko-framework, Brand-Symbole nutzen Symbol.for) und ist deshalb eine plain dependency, keine peerDependency.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -122,6 +122,10 @@
|
|
|
122
122
|
"types": "./src/executor-brand.ts",
|
|
123
123
|
"default": "./src/executor-brand.ts"
|
|
124
124
|
},
|
|
125
|
+
"./tenancy-brand": {
|
|
126
|
+
"types": "./src/tenancy-brand.ts",
|
|
127
|
+
"default": "./src/tenancy-brand.ts"
|
|
128
|
+
},
|
|
125
129
|
"./where-clause-types": {
|
|
126
130
|
"types": "./src/where-clause-types.ts",
|
|
127
131
|
"default": "./src/where-clause-types.ts"
|
package/src/define-handler.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { KumikoEventTypeMap } from "./event-type-map";
|
|
|
3
3
|
import type {
|
|
4
4
|
AccessRule,
|
|
5
5
|
AgentHandlerHints,
|
|
6
|
+
EscapeHatchDeclaration,
|
|
6
7
|
HandlerContext,
|
|
7
8
|
QueryEvent,
|
|
8
9
|
RateLimitOption,
|
|
@@ -34,11 +35,12 @@ export type WriteHandlerDefinition<
|
|
|
34
35
|
> = {
|
|
35
36
|
readonly name: TName;
|
|
36
37
|
readonly schema: TSchema;
|
|
37
|
-
readonly access
|
|
38
|
+
readonly access: AccessRule;
|
|
38
39
|
readonly description?: string;
|
|
39
40
|
readonly agent?: AgentHandlerHints;
|
|
40
41
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
41
42
|
readonly rateLimit?: RateLimitOption;
|
|
43
|
+
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
42
44
|
readonly handler: (
|
|
43
45
|
event: WriteEvent<z.infer<TSchema>>,
|
|
44
46
|
context: HandlerContext<TMap>,
|
|
@@ -61,11 +63,12 @@ export type WriteHandlerInput<
|
|
|
61
63
|
> = {
|
|
62
64
|
readonly name: TName;
|
|
63
65
|
readonly schema: TSchema;
|
|
64
|
-
readonly access
|
|
66
|
+
readonly access: AccessRule;
|
|
65
67
|
readonly description?: string;
|
|
66
68
|
readonly agent?: AgentHandlerHints;
|
|
67
69
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
68
70
|
readonly rateLimit?: RateLimitOption;
|
|
71
|
+
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
69
72
|
} & (
|
|
70
73
|
| {
|
|
71
74
|
readonly handler: (
|
|
@@ -90,7 +93,7 @@ export type QueryHandlerDefinition<
|
|
|
90
93
|
> = {
|
|
91
94
|
readonly name: TName;
|
|
92
95
|
readonly schema: TSchema;
|
|
93
|
-
readonly access
|
|
96
|
+
readonly access: AccessRule;
|
|
94
97
|
readonly description?: string;
|
|
95
98
|
readonly agent?: AgentHandlerHints;
|
|
96
99
|
readonly rateLimit?: RateLimitOption;
|
|
@@ -120,7 +123,7 @@ export type StreamHandlerDefinition<
|
|
|
120
123
|
> = {
|
|
121
124
|
readonly name: TName;
|
|
122
125
|
readonly schema: TSchema;
|
|
123
|
-
readonly access
|
|
126
|
+
readonly access: AccessRule;
|
|
124
127
|
readonly rateLimit?: RateLimitOption;
|
|
125
128
|
readonly handler: (
|
|
126
129
|
query: QueryEvent<z.infer<TSchema>>,
|
package/src/entity-handlers.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { EntityDefinition } from "./fields";
|
|
|
2
2
|
import type { AccessRule, AgentHandlerHints, QueryHandlerDef, WriteHandlerDef } from "./handlers";
|
|
3
3
|
|
|
4
4
|
export type EntityHandlerOptions = {
|
|
5
|
-
readonly access
|
|
5
|
+
readonly access: AccessRule;
|
|
6
6
|
readonly description?: string;
|
|
7
7
|
readonly agent?: AgentHandlerHints;
|
|
8
8
|
/** Reads and writes across every tenant instead of the caller's own — for a
|
|
@@ -26,9 +26,14 @@ export type EntityQueryHandlerOptions = EntityHandlerOptions;
|
|
|
26
26
|
|
|
27
27
|
export type EntityCrudVerb = "create" | "update" | "delete" | "restore" | "list" | "detail";
|
|
28
28
|
|
|
29
|
+
// `access` stays optional here — registerEntityCrud resolves it per-verb from `verbAccess`, falling back to this default.
|
|
30
|
+
export type EntityCrudHandlerDefaults = Omit<EntityHandlerOptions, "access"> & {
|
|
31
|
+
readonly access?: AccessRule;
|
|
32
|
+
};
|
|
33
|
+
|
|
29
34
|
export type RegisterEntityCrudOptions = {
|
|
30
|
-
readonly write?:
|
|
31
|
-
readonly read?:
|
|
35
|
+
readonly write?: EntityCrudHandlerDefaults;
|
|
36
|
+
readonly read?: EntityCrudHandlerDefaults;
|
|
32
37
|
readonly verbs?: Partial<Record<EntityCrudVerb, boolean>>;
|
|
33
38
|
/** Per-verb access override — falls back to `write.access`/`read.access` when unset for a verb. */
|
|
34
39
|
readonly verbAccess?: Partial<Record<EntityCrudVerb, AccessRule>>;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// (crypto/DB deps).
|
|
6
6
|
|
|
7
7
|
import type { EntityRelations } from "./relations";
|
|
8
|
+
import type { EntityTenancy } from "./tenancy-brand";
|
|
8
9
|
|
|
9
10
|
// PG type repertoire the read-model tables need. Deliberately narrow — no
|
|
10
11
|
// vendor-specific types (TSVECTOR, HSTORE, etc.). An app-author who needs
|
|
@@ -80,6 +81,9 @@ export type EntityTableMeta = {
|
|
|
80
81
|
// deriveEntityTableMeta so the registry can reject r.storeTable stores
|
|
81
82
|
// whose direct writes would skip the executor's encryption (#820).
|
|
82
83
|
readonly piiSubjectFields?: readonly string[];
|
|
84
|
+
// Only present for "global" tables — absent means "tenant" so existing
|
|
85
|
+
// schema snapshots don't drift. See EntityDefinition.tenancy.
|
|
86
|
+
readonly tenancy?: "global";
|
|
83
87
|
};
|
|
84
88
|
|
|
85
89
|
export type BuildEntityTableMetaOptions = {
|
|
@@ -93,4 +97,5 @@ export type UnmanagedTableInput = {
|
|
|
93
97
|
readonly columns: readonly ColumnMeta[];
|
|
94
98
|
readonly indexes?: readonly IndexMeta[];
|
|
95
99
|
readonly compositePrimaryKey?: CompositePrimaryKeyMeta;
|
|
100
|
+
readonly tenancy?: EntityTenancy;
|
|
96
101
|
};
|
package/src/feature.ts
CHANGED
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
ClaimKeyType,
|
|
36
36
|
DeclarativeEventMigration,
|
|
37
37
|
EntityRef,
|
|
38
|
+
EscapeHatchDeclaration,
|
|
38
39
|
EventDef,
|
|
39
40
|
EventMigrationDef,
|
|
40
41
|
EventPiiStance,
|
|
@@ -454,11 +455,12 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
454
455
|
name: string,
|
|
455
456
|
schema: TSchema,
|
|
456
457
|
handler: WriteHandlerFn<z.infer<TSchema>>,
|
|
457
|
-
options
|
|
458
|
-
access
|
|
458
|
+
options: {
|
|
459
|
+
access: AccessRule;
|
|
459
460
|
rateLimit?: RateLimitOption;
|
|
460
461
|
description?: string;
|
|
461
462
|
agent?: AgentHandlerHints;
|
|
463
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
462
464
|
},
|
|
463
465
|
): HandlerRef;
|
|
464
466
|
|
|
@@ -469,8 +471,8 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
469
471
|
name: string,
|
|
470
472
|
schema: TSchema,
|
|
471
473
|
handler: QueryHandlerFn<z.infer<TSchema>>,
|
|
472
|
-
options
|
|
473
|
-
access
|
|
474
|
+
options: {
|
|
475
|
+
access: AccessRule;
|
|
474
476
|
rateLimit?: RateLimitOption;
|
|
475
477
|
outputSchema?: ZodType;
|
|
476
478
|
description?: string;
|
|
@@ -485,7 +487,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
485
487
|
name: string,
|
|
486
488
|
schema: TSchema,
|
|
487
489
|
handler: StreamHandlerFn<z.infer<TSchema>>,
|
|
488
|
-
options
|
|
490
|
+
options: { access: AccessRule; rateLimit?: RateLimitOption },
|
|
489
491
|
): HandlerRef;
|
|
490
492
|
|
|
491
493
|
relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
|
package/src/fields.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { VariantSpec } from "./derivatives-types";
|
|
|
8
8
|
// array are auto-normalized to { [role]: "all" } at registry build.
|
|
9
9
|
// Long-term: string[] disappears.
|
|
10
10
|
import type { OwnershipMap } from "./ownership";
|
|
11
|
+
import type { EntityTenancy } from "./tenancy-brand";
|
|
11
12
|
|
|
12
13
|
export type FieldAccess = {
|
|
13
14
|
readonly read?: OwnershipMap | readonly string[];
|
|
@@ -914,10 +915,16 @@ export type ParentRefDef = {
|
|
|
914
915
|
readonly allowedTypes?: readonly string[];
|
|
915
916
|
};
|
|
916
917
|
|
|
917
|
-
export type EntityDefinition<
|
|
918
|
+
export type EntityDefinition<
|
|
919
|
+
F extends FieldsMap = FieldsMap,
|
|
920
|
+
T extends EntityTenancy = EntityTenancy,
|
|
921
|
+
> = {
|
|
918
922
|
readonly table?: string;
|
|
919
923
|
readonly fields: F;
|
|
920
924
|
readonly softDelete?: boolean;
|
|
925
|
+
/** "tenant" (default): auto-filtered by every db accessor. "global": shared across
|
|
926
|
+
* tenants, reachable only via `db.global(table)`. Not named `scope` — config keys use that. */
|
|
927
|
+
readonly tenancy?: T;
|
|
921
928
|
/** This aggregate's event stream lives on SYSTEM_TENANT_ID rather than the
|
|
922
929
|
* creator's tenant. Opt-in per entity (NOT inherited from r.systemScope()):
|
|
923
930
|
* only for genuinely tenant-independent aggregates like `user`. The first
|
package/src/handlers.ts
CHANGED
|
@@ -16,11 +16,35 @@ import type { TzContext } from "./tz-context";
|
|
|
16
16
|
|
|
17
17
|
// --- Access ---
|
|
18
18
|
|
|
19
|
+
export type OpenToAllDeclaration = { readonly reason: string };
|
|
20
|
+
|
|
21
|
+
export type OpenToAllAccessRule = {
|
|
22
|
+
// `true` is the deprecated pre-#2855 form, kept until the call-site migration (fw#2854).
|
|
23
|
+
readonly openToAll: OpenToAllDeclaration | true;
|
|
24
|
+
// Write handler intentionally accepts personal data from any authenticated caller (boot-validator gate).
|
|
25
|
+
readonly publicIntake?: true;
|
|
26
|
+
};
|
|
27
|
+
|
|
19
28
|
// AccessRule is DEFAULT-DENY: a handler without an access rule is not reachable.
|
|
20
29
|
// To grant access, set one of:
|
|
21
|
-
// - { roles: ["Admin", ...] }
|
|
22
|
-
// - { openToAll:
|
|
23
|
-
|
|
30
|
+
// - { roles: ["Admin", ...] } — role-based allowlist (empty array denies everyone)
|
|
31
|
+
// - { openToAll: { reason: "..." } } — any authenticated user may call (still requires a valid JWT)
|
|
32
|
+
// - { openToAll: true } — deprecated pre-#2855 form, still accepted
|
|
33
|
+
export type AccessRule = { readonly roles: readonly string[] } | OpenToAllAccessRule;
|
|
34
|
+
|
|
35
|
+
export type EscapeHatchDeclaration = { readonly reason: string };
|
|
36
|
+
|
|
37
|
+
// AccessRule can arrive from untyped sources (pattern-library JSON, Designer)
|
|
38
|
+
// where `openToAll` doesn't actually match the declared union — narrow via
|
|
39
|
+
// `unknown` instead of trusting the static type, deny on anything malformed.
|
|
40
|
+
export function isOpenToAllGranted(rule: AccessRule): boolean {
|
|
41
|
+
if (!("openToAll" in rule)) return false;
|
|
42
|
+
const openToAll: unknown = rule.openToAll;
|
|
43
|
+
if (openToAll === true) return true;
|
|
44
|
+
if (typeof openToAll !== "object" || openToAll === null) return false;
|
|
45
|
+
if (!("reason" in openToAll) || typeof openToAll.reason !== "string") return false;
|
|
46
|
+
return openToAll.reason.trim().length > 0;
|
|
47
|
+
}
|
|
24
48
|
|
|
25
49
|
// --- Pipeline User ---
|
|
26
50
|
|
|
@@ -996,11 +1020,12 @@ export type WriteHandlerDef = {
|
|
|
996
1020
|
readonly name: string;
|
|
997
1021
|
readonly schema: ZodType;
|
|
998
1022
|
readonly handler: WriteHandlerFn;
|
|
999
|
-
readonly access
|
|
1023
|
+
readonly access: AccessRule;
|
|
1000
1024
|
readonly description?: string;
|
|
1001
1025
|
readonly agent?: AgentHandlerHints;
|
|
1002
1026
|
readonly unsafeSkipTransitionGuard?: boolean;
|
|
1003
1027
|
readonly rateLimit?: RateLimitOption;
|
|
1028
|
+
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
1004
1029
|
// Set when the author wrote a `perform: stepsPipeline(...)` block. Boot-
|
|
1005
1030
|
// validators (projection-allowlist) and Designer/AI tooling read this
|
|
1006
1031
|
// to inspect the step list. Absent on free-form handlers.
|
|
@@ -1025,7 +1050,7 @@ export type QueryHandlerDef = {
|
|
|
1025
1050
|
* read-gate. The boot-validator requires it on a `parentRef` entity's
|
|
1026
1051
|
* list/detail handler — see boot-validator/parent-ref.ts. */
|
|
1027
1052
|
readonly [ENTITY_CONVENTION_QUERY_BRAND]?: true;
|
|
1028
|
-
readonly access
|
|
1053
|
+
readonly access: AccessRule;
|
|
1029
1054
|
readonly description?: string;
|
|
1030
1055
|
readonly agent?: AgentHandlerHints;
|
|
1031
1056
|
readonly rateLimit?: RateLimitOption;
|
|
@@ -1044,6 +1069,6 @@ export type StreamHandlerDef = {
|
|
|
1044
1069
|
readonly name: string;
|
|
1045
1070
|
readonly schema: ZodType;
|
|
1046
1071
|
readonly handler: StreamHandlerFn;
|
|
1047
|
-
readonly access
|
|
1072
|
+
readonly access: AccessRule;
|
|
1048
1073
|
readonly rateLimit?: RateLimitOption;
|
|
1049
1074
|
};
|
package/src/screen.ts
CHANGED
|
@@ -530,6 +530,10 @@ export type MetricNavigate = {
|
|
|
530
530
|
readonly entity?: string;
|
|
531
531
|
readonly entityId?: string;
|
|
532
532
|
readonly params?: RowFieldExtractor;
|
|
533
|
+
/** Section id of the tab to activate at the destination. Alone (no
|
|
534
|
+
* `screen`/`entity`), it activates that tab on the current record
|
|
535
|
+
* instead of navigating away. */
|
|
536
|
+
readonly tab?: string;
|
|
533
537
|
};
|
|
534
538
|
|
|
535
539
|
// A metric can navigate on click, so the plain string shorthand (field name,
|
|
@@ -1026,8 +1030,13 @@ export type EntityEditScreenDefinition = {
|
|
|
1026
1030
|
* feature to the target at boot time — an app that doesn't mount the
|
|
1027
1031
|
* target feature fails the boot-validator; only use this form in a
|
|
1028
1032
|
* reusable feature when the target feature is guaranteed to be
|
|
1029
|
-
* mounted alongside it.
|
|
1030
|
-
|
|
1033
|
+
* mounted alongside it.
|
|
1034
|
+
*
|
|
1035
|
+
* The object form additionally names the success-payload field the
|
|
1036
|
+
* navigation id comes from (`ActionFormRedirect.idFrom`) — needed when
|
|
1037
|
+
* the edited record is a child and the target screen is the parent's
|
|
1038
|
+
* detail screen. */
|
|
1039
|
+
readonly redirect?: string | ActionFormRedirect;
|
|
1031
1040
|
/** Parent list screen (short id) for breadcrumb + nav highlighting when
|
|
1032
1041
|
* this screen has no nav entry of its own. Same field/semantics as on
|
|
1033
1042
|
* `custom`/`projectionDetail`; an explicit value here wins over the
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
}
|
package/src/tenant-db-types.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { DbRunner } from "./db-connection";
|
|
2
2
|
import type { EntityTableMeta } from "./entity-table-meta-types";
|
|
3
|
-
import type { NotExecutorOnly } from "./executor-brand";
|
|
3
|
+
import type { ExecutorOnly, NotExecutorOnly } from "./executor-brand";
|
|
4
4
|
import type { TenantId } from "./identifiers";
|
|
5
5
|
import type { SchemaTable } from "./schema-table-types";
|
|
6
|
+
import type { TenancyBrand } from "./tenancy-brand";
|
|
6
7
|
import type { SelectOptions, WhereObject } from "./where-clause-types";
|
|
7
8
|
|
|
8
9
|
// Method-form writes reject the executor-only brand exactly like the free-function
|
|
@@ -14,6 +15,28 @@ import type { SelectOptions, WhereObject } from "./where-clause-types";
|
|
|
14
15
|
// the plain `SchemaTable` param.
|
|
15
16
|
type WritableTable = (SchemaTable | EntityTableMeta) & NotExecutorOnly;
|
|
16
17
|
|
|
18
|
+
// db.global(table)'s surface: reads are unrestricted; writes require the managed
|
|
19
|
+
// EntityTable's executor-only brand to be absent (mirrors WritableTable above).
|
|
20
|
+
type GlobalReads = {
|
|
21
|
+
selectMany<T = Record<string, unknown>>(
|
|
22
|
+
where?: WhereObject,
|
|
23
|
+
options?: SelectOptions,
|
|
24
|
+
): Promise<readonly T[]>;
|
|
25
|
+
fetchOne<T = Record<string, unknown>>(where: WhereObject): Promise<T | undefined>;
|
|
26
|
+
};
|
|
27
|
+
type GlobalWrites = {
|
|
28
|
+
insertOne<T = Record<string, unknown>>(values: Record<string, unknown>): Promise<T | undefined>;
|
|
29
|
+
updateMany<T = Record<string, unknown>>(
|
|
30
|
+
set: Record<string, unknown>,
|
|
31
|
+
where: WhereObject,
|
|
32
|
+
): Promise<readonly T[]>;
|
|
33
|
+
deleteMany(where: WhereObject): Promise<void>;
|
|
34
|
+
};
|
|
35
|
+
// Checks `extends ExecutorOnly`, not `NotExecutorOnly` — the latter's optional-never
|
|
36
|
+
// property is a TS "weak type" that would silently collapse every TTable to GlobalReads.
|
|
37
|
+
export type GlobalTableDb<TTable> = GlobalReads &
|
|
38
|
+
(TTable extends ExecutorOnly ? unknown : GlobalWrites);
|
|
39
|
+
|
|
17
40
|
/**
|
|
18
41
|
* TenantDb scope modes:
|
|
19
42
|
*
|
|
@@ -34,8 +57,18 @@ export type TenantDb = {
|
|
|
34
57
|
* Underlying DbRunner. Framework-internal use (event-store, migrations) —
|
|
35
58
|
* bypasses tenant-filter. Feature code uses the typed helpers above so the
|
|
36
59
|
* automatic scoping stays intact.
|
|
60
|
+
* @deprecated Use `ctx.systemDb.unsafeRaw(reason)` or `db.global(table)`
|
|
61
|
+
* instead — both make the cross-tenant intent an explicit, named
|
|
62
|
+
* declaration instead of a silent unfiltered escape hatch. Removal fw#2860.
|
|
37
63
|
*/
|
|
38
64
|
readonly raw: DbRunner;
|
|
65
|
+
/**
|
|
66
|
+
* Reach a "global" table with the tenant filter lifted — reads always work; writes
|
|
67
|
+
* reject unless the write handler declared `escapeHatch: { reason }`. "tenant"-tenancy is a compile error here.
|
|
68
|
+
*/
|
|
69
|
+
global<TTable extends (SchemaTable | EntityTableMeta) & TenancyBrand<"global">>(
|
|
70
|
+
table: TTable,
|
|
71
|
+
): GlobalTableDb<TTable>;
|
|
39
72
|
selectMany<T = Record<string, unknown>>(
|
|
40
73
|
table: SchemaTable,
|
|
41
74
|
where?: WhereObject,
|
|
@@ -68,6 +101,11 @@ export type UncheckedSystemDb = {
|
|
|
68
101
|
assertTenantMatch(tenantId: TenantId): TenantDb;
|
|
69
102
|
assertRowsTenant<T>(rows: readonly T[], tenantField: keyof T): readonly T[];
|
|
70
103
|
acknowledgeCrossTenant(reason: string): TenantDb;
|
|
104
|
+
/**
|
|
105
|
+
* Raw unfiltered DbRunner, gated behind a mandatory non-empty `reason` for auditability.
|
|
106
|
+
* Throws on an empty (or whitespace-only) reason.
|
|
107
|
+
*/
|
|
108
|
+
unsafeRaw(reason: string): DbRunner;
|
|
71
109
|
// Same self-check pair as above, but hands back the caller's
|
|
72
110
|
// ctx.dbOutsideTransaction TenantDb instead of the in-tx one — for
|
|
73
111
|
// durability writes that must survive a rollback of the handler's own
|