@cosmicdrift/kumiko-types 0.166.0 → 0.167.1

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/README.md CHANGED
@@ -7,9 +7,10 @@ Framework type definitions for Kumiko — `FeatureDefinition`, boot-check types,
7
7
  and the pure engine types. Lets downstream consumers build against the type
8
8
  contracts without importing the whole framework package.
9
9
 
10
- Also contains the identity-sensitive error classes (`event-store-errors.ts`,
11
- `kms-adapter-types.ts`) as runtime code `kumiko-framework`/
12
- `kumiko-bundled-features` declare this package as a `peerDependency`
13
- (single-copy constraint), not a plain dependency.
10
+ Carries no identity-sensitive runtime values: no classes, and brand symbols use
11
+ `Symbol.for`. Resolving two copies of this package is therefore harmless, so
12
+ `kumiko-framework`/`kumiko-bundled-features` declare it as a plain dependency.
13
+ The error classes callers branch on with `instanceof` live in `kumiko-framework`
14
+ (`/event-store`, `/crypto`), which stays a single copy.
14
15
 
15
16
  See the [monorepo root README](../../README.md) for the broader pitch.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-types",
3
- "version": "0.166.0",
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 auch die identitaets-sensitiven Error-Klassen (event-store-errors.ts, kms-adapter-types.ts) als Runtime-Code kumiko-framework/kumiko-bundled-features deklarieren dieses Package deshalb als peerDependency (Single-Copy-Zwang), nicht als plain dependency.",
3
+ "version": "0.167.1",
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>",
7
7
  "repository": {
@@ -206,10 +206,6 @@
206
206
  "types": "./src/secrets-types.ts",
207
207
  "default": "./src/secrets-types.ts"
208
208
  },
209
- "./event-store-errors": {
210
- "types": "./src/event-store-errors.ts",
211
- "default": "./src/event-store-errors.ts"
212
- },
213
209
  "./kms-adapter-types": {
214
210
  "types": "./src/kms-adapter-types.ts",
215
211
  "default": "./src/kms-adapter-types.ts"
@@ -228,12 +224,12 @@
228
224
  }
229
225
  },
230
226
  "devDependencies": {
231
- "hono": "^4.12.18",
227
+ "hono": "^4.12.27",
232
228
  "postgres": "^3.4.9",
233
229
  "temporal-polyfill": "^0.3.2"
234
230
  },
235
231
  "peerDependencies": {
236
- "hono": "^4.12.18",
232
+ "hono": "^4.12.27",
237
233
  "postgres": "^3.4.9",
238
234
  "temporal-polyfill": "^0.3.2"
239
235
  },
@@ -1,5 +1,13 @@
1
1
  import type { TenantId } from "./identifiers";
2
2
 
3
+ // Structural shape of kumiko-framework's VersionConflictError. The class
4
+ // itself cannot live here (#1629) — this package must stay free of
5
+ // identity-sensitive runtime values so it can be a plain dependency.
6
+ export interface VersionConflict extends Error {
7
+ readonly aggregateId: string;
8
+ readonly expectedVersion: number;
9
+ }
10
+
3
11
  export type EventMetadata = {
4
12
  readonly userId: string;
5
13
  readonly requestId?: string;
package/src/handlers.ts CHANGED
@@ -30,6 +30,10 @@ export type SessionUser = {
30
30
  readonly id: string;
31
31
  readonly tenantId: TenantId;
32
32
  readonly roles: readonly string[];
33
+ // IANA zone from user.timezone, set at login. Absent → ctx.tz falls back
34
+ // to tenant.timezone, then "UTC" (buildHandlerContext, fw#1636). Stale
35
+ // until re-login — same tradeoff as `roles`.
36
+ readonly timezone?: string;
33
37
  // App-specific identity facts baked into the JWT at login time.
34
38
  // Populated by `r.authClaims()` hooks (not yet implemented — see the
35
39
  // auth-claims design note in docs/plans). Reserved here so the type shape
@@ -479,12 +483,14 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
479
483
  readonly metrics: MetricsHandle;
480
484
  readonly tracer: Tracer;
481
485
 
482
- // Time + TZ helper. Feature-Code MUSS hier durch statt `new Date()`
483
- // ctx.tz.now() liefert Temporal.Instant, ctx.tz.parse(wallClock, tz)
484
- // produziert ZonedDateTime, ctx.tz.toLocatedJson serialisiert für die
485
- // API-Boundary. Lint-Regel gegen `new Date()` kommt sobald alle internen
486
- // usages migriert sind. Tenant + User-TZ defaults aktuell "UTC", werden
487
- // aus tenant.timezone / user.timezone gelesen sobald die Felder existieren.
486
+ // Time + TZ helper. Feature code MUST go through this instead of
487
+ // `new Date()` — ctx.tz.now() returns Temporal.Instant, ctx.tz.parse
488
+ // (wallClock, tz) produces a ZonedDateTime, ctx.tz.toLocatedJson
489
+ // serializes for the API boundary. The lint rule against `new Date()`
490
+ // lands once all internal usages are migrated. tenant reads
491
+ // tenant:config:timezone (falls back to "UTC" when no config feature is
492
+ // mounted or the value is unset); user reads SessionUser.timezone (falls
493
+ // back to tenant).
488
494
  readonly tz: TzContext;
489
495
 
490
496
  // Resolve every registered r.authClaims() hook against `user` and return
@@ -689,7 +695,7 @@ export type UnsafeAppendEventFn = (args: AppendEventArgs) => Promise<void>;
689
695
  // entire enclosing transaction.
690
696
  export type TryAppendEventResult =
691
697
  | { readonly ok: true; readonly event: import("./event-store-types").StoredEvent }
692
- | { readonly ok: false; readonly conflict: import("./event-store-errors").VersionConflictError };
698
+ | { readonly ok: false; readonly conflict: import("./event-store-types").VersionConflict };
693
699
 
694
700
  export type TryAppendEventFn = (args: AppendEventArgs) => Promise<TryAppendEventResult>;
695
701
 
@@ -95,24 +95,3 @@ export type KmsAdapter = LocalKeyKmsAdapter | RemoteCryptoKmsAdapter;
95
95
  export function isLocalKeyKmsAdapter(adapter: KmsAdapter): adapter is LocalKeyKmsAdapter {
96
96
  return adapter.capabilities.mode === "local-key";
97
97
  }
98
-
99
- export class KeyErasedError extends Error {
100
- constructor(public readonly subject: SubjectId) {
101
- super(`Subject key erased: ${subjectIdToKey(subject)}`);
102
- this.name = "KeyErasedError";
103
- }
104
- }
105
-
106
- export class KeyNotFoundError extends Error {
107
- constructor(public readonly subject: SubjectId) {
108
- super(`Subject key not found: ${subjectIdToKey(subject)}`);
109
- this.name = "KeyNotFoundError";
110
- }
111
- }
112
-
113
- export class KeyAlreadyExistsError extends Error {
114
- constructor(public readonly subject: SubjectId) {
115
- super(`Subject key already exists: ${subjectIdToKey(subject)}`);
116
- this.name = "KeyAlreadyExistsError";
117
- }
118
- }
@@ -10,9 +10,14 @@ import type { TenantId } from "./identifiers";
10
10
  // intentional. Framework code that sees `Secret<string>` knows the caller
11
11
  // has already gone through the audited ctx.secrets.get path.
12
12
  //
13
- // The brand is a real (non-registered) Symbol so it exists at runtime for
14
- // isSecret() without clashing with user-land symbols of the same name.
15
- const SecretBrand: unique symbol = Symbol("kumiko.secret");
13
+ // Registered symbol, matching KUMIKO_*_SYMBOL in schema-table-types.ts. A
14
+ // plain Symbol() is per-copy: two resolved copies of this package brand with
15
+ // two different symbols, so createSecret() from one and isSecret() from the
16
+ // other disagree — and isSecret() is the ONLY thing assertNoSecretLeak has,
17
+ // so a false there serializes the plaintext into the response. The namespaced
18
+ // key makes accidental user-land collision implausible; a deliberate forge
19
+ // would only over-trigger the guard, which fails safe.
20
+ const SecretBrand: unique symbol = Symbol.for("kumiko.secret");
16
21
 
17
22
  export type Secret<T = string> = {
18
23
  readonly [SecretBrand]: true;
@@ -1,54 +0,0 @@
1
- // Failure modes of the event-store's append() path. Surfaced as typed
2
- // errors so the executor layer can map them to the framework's
3
- // WriteResult error contract (version_conflict).
4
-
5
- export class VersionConflictError extends Error {
6
- public readonly aggregateId: string;
7
- public readonly expectedVersion: number;
8
- constructor(aggregateId: string, expectedVersion: number) {
9
- super(
10
- `Version conflict on aggregate ${aggregateId}: expected predecessor version ${expectedVersion}`,
11
- );
12
- this.name = "VersionConflictError";
13
- this.aggregateId = aggregateId;
14
- this.expectedVersion = expectedVersion;
15
- }
16
- }
17
-
18
- // Thrown when append() collides on the partial unique index over
19
- // metadata.idempotencyKey (tenant-scoped). Distinct from VersionConflictError:
20
- // a version conflict means two writers raced the same predecessor; this
21
- // means the same idempotency key was used twice, which is a caller-side
22
- // retry that must have already appended once. Callers that set
23
- // idempotencyKey should treat this as "already applied" rather than retry.
24
- export class IdempotentAppendConflictError extends Error {
25
- public readonly tenantId: string;
26
- public readonly idempotencyKey: string;
27
- constructor(tenantId: string, idempotencyKey: string) {
28
- super(
29
- `Idempotency conflict on tenant ${tenantId}: an event with idempotencyKey "${idempotencyKey}" was already appended.`,
30
- );
31
- this.name = "IdempotentAppendConflictError";
32
- this.tenantId = tenantId;
33
- this.idempotencyKey = idempotencyKey;
34
- }
35
- }
36
-
37
- // Thrown when ctx.appendEvent targets an archived stream. Archived aggregates
38
- // are read-only — restoreStream() makes them writable again. The archive
39
- // state is not carried on the events themselves; it lives on the sparse
40
- // kumiko_archived_streams table. Handlers that need to branch on archive
41
- // state should call ctx.isStreamArchived(id) first.
42
- export class ArchivedStreamError extends Error {
43
- public readonly tenantId: string;
44
- public readonly aggregateId: string;
45
- constructor(tenantId: string, aggregateId: string) {
46
- super(
47
- `Aggregate ${aggregateId} on tenant ${tenantId} is archived — appendEvent is blocked. ` +
48
- `Call restoreStream() to re-open the stream before writing.`,
49
- );
50
- this.name = "ArchivedStreamError";
51
- this.tenantId = tenantId;
52
- this.aggregateId = aggregateId;
53
- }
54
- }