@cosmicdrift/kumiko-types 0.266.0 → 0.269.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 +1 -1
- package/src/define-handler.ts +4 -0
- package/src/feature.ts +6 -2
- package/src/handlers.ts +28 -2
- package/src/tenant-db-types.ts +9 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.269.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>",
|
package/src/define-handler.ts
CHANGED
|
@@ -126,6 +126,10 @@ export type StreamHandlerDefinition<
|
|
|
126
126
|
readonly schema: TSchema;
|
|
127
127
|
readonly access: AccessRule;
|
|
128
128
|
readonly rateLimit?: RateLimitOption;
|
|
129
|
+
// Stream handlers can't reach db.global() (that gate is write-only), but
|
|
130
|
+
// they can still switch identity to SYSTEM via ctx.queryAs — this opts
|
|
131
|
+
// in, same contract as WriteHandlerDefinition.escapeHatch.
|
|
132
|
+
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
129
133
|
readonly handler: (
|
|
130
134
|
query: QueryEvent<z.infer<TSchema>>,
|
|
131
135
|
context: HandlerContext<TMap>,
|
package/src/feature.ts
CHANGED
|
@@ -488,7 +488,11 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
488
488
|
name: string,
|
|
489
489
|
schema: TSchema,
|
|
490
490
|
handler: StreamHandlerFn<z.infer<TSchema>>,
|
|
491
|
-
options: {
|
|
491
|
+
options: {
|
|
492
|
+
access: AccessRule;
|
|
493
|
+
rateLimit?: RateLimitOption;
|
|
494
|
+
escapeHatch?: EscapeHatchDeclaration;
|
|
495
|
+
},
|
|
492
496
|
): HandlerRef;
|
|
493
497
|
|
|
494
498
|
relation(entity: NameOrRef, relationName: string, definition: RelationDefinition): void;
|
|
@@ -501,7 +505,7 @@ export type FeatureRegistrar<TFeature extends string = string> = {
|
|
|
501
505
|
): void;
|
|
502
506
|
|
|
503
507
|
hook(type: "validation", target: RefOrRefs, fn: ValidationHookFn): void;
|
|
504
|
-
// escapeHatch grants this hook (not the handler)
|
|
508
|
+
// escapeHatch grants this hook (not the handler) identity-switches beyond its caller — see system-identity-switch.ts.
|
|
505
509
|
hook(
|
|
506
510
|
type: "preSave",
|
|
507
511
|
target: RefOrRefs,
|
package/src/handlers.ts
CHANGED
|
@@ -52,6 +52,10 @@ export function isOpenToAllGranted(rule: AccessRule): boolean {
|
|
|
52
52
|
|
|
53
53
|
// --- Pipeline User ---
|
|
54
54
|
|
|
55
|
+
// Set only on a SessionUser the framework resolved internally for a
|
|
56
|
+
// background read (ctx.queryAsMember) — such a principal never carries `sid`.
|
|
57
|
+
export type SessionUserOrigin = "member-resolution";
|
|
58
|
+
|
|
55
59
|
export type SessionUser = {
|
|
56
60
|
// UUID-string so user.id threads through the event-store (aggregate-id) and
|
|
57
61
|
// the projection tables (uuid PK) without casts. Auth middleware reads the
|
|
@@ -92,6 +96,7 @@ export type SessionUser = {
|
|
|
92
96
|
readonly scopes: readonly string[];
|
|
93
97
|
readonly allowedQns: readonly string[];
|
|
94
98
|
};
|
|
99
|
+
readonly origin?: SessionUserOrigin;
|
|
95
100
|
};
|
|
96
101
|
|
|
97
102
|
// --- Claim Keys (r.claimKey declarations) ---
|
|
@@ -231,6 +236,9 @@ import type { Registry } from "./feature";
|
|
|
231
236
|
import type { TenantId } from "./identifiers";
|
|
232
237
|
import type { UncheckedSystemDb } from "./tenant-db-types";
|
|
233
238
|
|
|
239
|
+
// The framework resolves the member internally, so no hand-built SessionUser reaches app code.
|
|
240
|
+
export type MemberReader = (userId: string, qn: string, payload: unknown) => Promise<unknown>;
|
|
241
|
+
|
|
234
242
|
// Minimal interface for job event triggers (framework-owned, concrete type in jobs/)
|
|
235
243
|
export type JobRunnerRef = {
|
|
236
244
|
handleEvent(
|
|
@@ -249,6 +257,9 @@ export type JobRunnerRef = {
|
|
|
249
257
|
export type DispatchWriteRef = {
|
|
250
258
|
readonly write: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
|
|
251
259
|
readonly queryAs: (user: SessionUser, qn: string, payload: unknown) => Promise<unknown>;
|
|
260
|
+
// Builds a tenant-scoped MemberReader — one per JobContext.queryAsMember
|
|
261
|
+
// caller (job-runner.ts lazily creates one per job run).
|
|
262
|
+
readonly createMemberReader: (tenantId: TenantId) => MemberReader;
|
|
252
263
|
};
|
|
253
264
|
|
|
254
265
|
// Priority levels for notifications
|
|
@@ -355,6 +366,9 @@ type SharedContextFields = {
|
|
|
355
366
|
// hooks synchronously (kumiko-framework#1566). Absent outside a write
|
|
356
367
|
// pipeline — callers fall back to immediate fire (fixture / no-tx paths).
|
|
357
368
|
readonly scheduleAfterCommit?: (hook: () => Promise<void>) => void;
|
|
369
|
+
// Present on HandlerContext/JobContext; hooks receive HandlerContext as
|
|
370
|
+
// AppContext, so it's optional here. See HandlerContext.queryAsMember.
|
|
371
|
+
readonly queryAsMember?: MemberReader;
|
|
358
372
|
};
|
|
359
373
|
|
|
360
374
|
// All optional — used at pipeline/system boundaries.
|
|
@@ -401,8 +415,9 @@ export type AppContext = SharedContextFields & {
|
|
|
401
415
|
// sharing the active tx + afterCommit queue. Field-access filters apply.
|
|
402
416
|
// ctx.queryAs / ctx.writeAs switch identity (e.g. SYSTEM for privileged
|
|
403
417
|
// lookups like "find user by email for auth" — system reads aren't filtered
|
|
404
|
-
// by field-access read rules).
|
|
405
|
-
// only from an r.systemScope()
|
|
418
|
+
// by field-access read rules). Any target other than the caller itself (or
|
|
419
|
+
// a subset of its roles) is gated: reachable only from an r.systemScope()
|
|
420
|
+
// feature, a job, or a handler/hook that
|
|
406
421
|
// declared { escapeHatch: { reason } } (system-identity-switch.ts).
|
|
407
422
|
//
|
|
408
423
|
// The design: handlers are the contract between features. Feature A requires
|
|
@@ -640,6 +655,10 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
|
|
|
640
655
|
userId: string,
|
|
641
656
|
tenantId: TenantId,
|
|
642
657
|
) => Promise<ActiveMembershipResult>;
|
|
658
|
+
|
|
659
|
+
// Read-only principal without `sid`; needs the same grant as a SYSTEM queryAs
|
|
660
|
+
// (membership is resolved as SYSTEM), cached per handler invocation or job run.
|
|
661
|
+
readonly queryAsMember: MemberReader;
|
|
643
662
|
};
|
|
644
663
|
|
|
645
664
|
// Job execution: db + registry + systemUser + logging guaranteed, plus a
|
|
@@ -686,6 +705,9 @@ export type JobContext = SharedContextFields & {
|
|
|
686
705
|
readonly write: (qn: string, payload: unknown) => Promise<WriteResult>;
|
|
687
706
|
readonly writeAs: (user: SessionUser, qn: string, payload: unknown) => Promise<WriteResult>;
|
|
688
707
|
readonly queryAs: (user: SessionUser, qn: string, payload: unknown) => Promise<unknown>;
|
|
708
|
+
// Tenant = the job's resolved tenant (may originate from payload.tenantId
|
|
709
|
+
// for tenant-less triggers, see _tenantId below). Ungated, like queryAs.
|
|
710
|
+
readonly queryAsMember: MemberReader;
|
|
689
711
|
// Multi-trigger jobs (`on: [...]`) use this to tell which trigger fired —
|
|
690
712
|
// undefined for cron/manual jobs. Mirrors AppContext.triggerName.
|
|
691
713
|
readonly triggerName?: string;
|
|
@@ -1107,4 +1129,8 @@ export type StreamHandlerDef = {
|
|
|
1107
1129
|
readonly handler: StreamHandlerFn;
|
|
1108
1130
|
readonly access: AccessRule;
|
|
1109
1131
|
readonly rateLimit?: RateLimitOption;
|
|
1132
|
+
// Stream handlers can't reach db.global() (that gate is write-only), but
|
|
1133
|
+
// they can still switch identity to SYSTEM via ctx.queryAs — this opts
|
|
1134
|
+
// in, same contract as WriteHandlerDef.escapeHatch.
|
|
1135
|
+
readonly escapeHatch?: EscapeHatchDeclaration;
|
|
1110
1136
|
};
|
package/src/tenant-db-types.ts
CHANGED
|
@@ -57,11 +57,16 @@ export type TenantDb = {
|
|
|
57
57
|
* Underlying DbRunner. Framework-internal use (event-store, migrations) —
|
|
58
58
|
* bypasses tenant-filter. Feature code uses the typed helpers above so the
|
|
59
59
|
* automatic scoping stays intact.
|
|
60
|
-
* @deprecated Use `ctx.
|
|
61
|
-
* instead — both make the cross-tenant intent an explicit, named
|
|
60
|
+
* @deprecated Use `ctx.db.unsafeRaw(reason)` / `db.global(table)` (method-
|
|
61
|
+
* form) instead — both make the cross-tenant intent an explicit, named
|
|
62
62
|
* declaration instead of a silent unfiltered escape hatch. Removal fw#2860.
|
|
63
63
|
*/
|
|
64
64
|
readonly raw: DbRunner;
|
|
65
|
+
/**
|
|
66
|
+
* Unfiltered DbRunner escape hatch for handlers/hooks that declare `escapeHatch: { reason }`.
|
|
67
|
+
* Throws `AccessDeniedError` when ungranted, or `Error` when `reason` is empty.
|
|
68
|
+
*/
|
|
69
|
+
unsafeRaw(reason: string): DbRunner;
|
|
65
70
|
/**
|
|
66
71
|
* Reach a "global" table with the tenant filter lifted — reads always work; writes
|
|
67
72
|
* reject unless the write handler declared `escapeHatch: { reason }`. "tenant"-tenancy is a compile error here.
|
|
@@ -70,12 +75,12 @@ export type TenantDb = {
|
|
|
70
75
|
table: TTable,
|
|
71
76
|
): GlobalTableDb<TTable>;
|
|
72
77
|
selectMany<T = Record<string, unknown>>(
|
|
73
|
-
table: SchemaTable,
|
|
78
|
+
table: SchemaTable | EntityTableMeta,
|
|
74
79
|
where?: WhereObject,
|
|
75
80
|
options?: SelectOptions,
|
|
76
81
|
): Promise<readonly T[]>;
|
|
77
82
|
fetchOne<T = Record<string, unknown>>(
|
|
78
|
-
table: SchemaTable,
|
|
83
|
+
table: SchemaTable | EntityTableMeta,
|
|
79
84
|
where: WhereObject,
|
|
80
85
|
): Promise<T | undefined>;
|
|
81
86
|
insertOne<T = Record<string, unknown>>(
|